summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt8
-rw-r--r--cmds/statsd/src/StatsLogProcessor.cpp18
-rw-r--r--cmds/statsd/src/atoms.proto27
-rw-r--r--cmds/statsd/src/logd/LogEvent.h6
-rw-r--r--cmds/statsd/src/metrics/CountMetricProducer.cpp3
-rw-r--r--cmds/statsd/src/metrics/DurationMetricProducer.cpp3
-rw-r--r--cmds/statsd/src/metrics/EventMetricProducer.cpp3
-rw-r--r--cmds/statsd/src/metrics/GaugeMetricProducer.cpp3
-rw-r--r--cmds/statsd/src/metrics/ValueMetricProducer.cpp3
-rw-r--r--cmds/uiautomator/instrumentation/Android.mk2
-rw-r--r--core/java/android/app/Activity.java31
-rw-r--r--core/java/android/app/ActivityManager.java25
-rw-r--r--core/java/android/app/Dialog.java35
-rw-r--r--core/java/android/app/IActivityManager.aidl3
-rw-r--r--core/java/android/app/Instrumentation.java7
-rw-r--r--core/java/android/app/NotificationManager.java12
-rw-r--r--core/java/android/app/Service.java12
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java29
-rw-r--r--core/java/android/app/backup/BackupManagerMonitor.java6
-rw-r--r--core/java/android/app/backup/BackupTransport.java29
-rw-r--r--core/java/android/os/Build.java8
-rw-r--r--core/java/android/provider/Settings.java20
-rw-r--r--core/java/android/provider/SettingsValidators.java4
-rw-r--r--core/java/android/service/dreams/DreamService.java37
-rw-r--r--core/java/android/util/FeatureFlagUtils.java1
-rw-r--r--core/java/android/util/apk/ApkVerityBuilder.java189
-rw-r--r--core/java/android/view/View.java25
-rw-r--r--core/java/android/view/Window.java30
-rw-r--r--core/java/android/webkit/WebView.java12
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java5
-rw-r--r--core/java/com/android/internal/app/IntentForwarderActivity.java2
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java38
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl2
-rw-r--r--core/jni/android/graphics/AnimatedImageDrawable.cpp39
-rw-r--r--core/proto/android/providers/settings.proto7
-rw-r--r--core/res/AndroidManifest.xml16
-rw-r--r--core/res/res/drawable/ic_info_outline_24.xml25
-rw-r--r--core/res/res/layout/app_error_dialog.xml68
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/strings.xml5
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/tests/coretests/AndroidManifest.xml1
-rw-r--r--core/tests/coretests/src/android/provider/SettingsBackupTest.java5
-rw-r--r--core/tests/coretests/src/android/provider/SettingsValidatorsTest.java92
-rw-r--r--core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java7
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedImageDrawable.java18
-rw-r--r--graphics/java/android/graphics/drawable/BitmapDrawable.java85
-rw-r--r--graphics/java/android/graphics/drawable/Drawable.java43
-rw-r--r--libs/hwui/Android.bp1
-rw-r--r--libs/hwui/RecordingCanvas.cpp4
-rw-r--r--libs/hwui/RecordingCanvas.h3
-rw-r--r--libs/hwui/SkiaCanvas.cpp14
-rw-r--r--libs/hwui/SkiaCanvas.h3
-rw-r--r--libs/hwui/hwui/AnimatedImageDrawable.cpp152
-rw-r--r--libs/hwui/hwui/AnimatedImageDrawable.h91
-rw-r--r--libs/hwui/hwui/Canvas.h4
-rw-r--r--libs/hwui/pipeline/skia/SkiaDisplayList.cpp16
-rw-r--r--libs/hwui/pipeline/skia/SkiaDisplayList.h2
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.cpp10
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.h12
-rw-r--r--libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp6
-rw-r--r--libs/hwui/pipeline/skia/SkiaRecordingCanvas.h1
-rw-r--r--media/java/android/media/AudioPort.java3
-rw-r--r--media/java/android/media/MediaBrowser2.java113
-rw-r--r--media/java/android/media/MediaController2.java450
-rw-r--r--media/java/android/media/MediaItem2.java146
-rw-r--r--media/java/android/media/MediaLibraryService2.java215
-rw-r--r--media/java/android/media/MediaMetadata2.java815
-rw-r--r--media/java/android/media/MediaSession2.java407
-rw-r--r--media/java/android/media/PlaybackState2.java200
-rw-r--r--media/java/android/media/Rating2.java304
-rw-r--r--media/java/android/media/update/MediaBrowser2Provider.java7
-rw-r--r--media/java/android/media/update/MediaController2Provider.java41
-rw-r--r--media/java/android/media/update/MediaLibraryService2Provider.java8
-rw-r--r--media/java/android/media/update/MediaSession2Provider.java21
-rw-r--r--media/java/android/media/update/StaticProvider.java17
-rw-r--r--media/java/android/media/update/TransportControlProvider.java8
-rw-r--r--nfc-extras/tests/Android.mk2
-rw-r--r--packages/MtpDocumentsProvider/AndroidManifest.xml1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java17
-rw-r--r--packages/Shell/AndroidManifest.xml1
-rw-r--r--packages/SystemUI/AndroidManifest.xml17
-rw-r--r--packages/SystemUI/res/layout/output_chooser.xml79
-rw-r--r--packages/SystemUI/res/layout/volume_dialog.xml2
-rw-r--r--packages/SystemUI/res/layout/wireless_charging_layout.xml56
-rw-r--r--packages/SystemUI/res/values/dimens.xml10
-rw-r--r--packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java213
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java163
-rw-r--r--packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java63
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java105
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java63
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java91
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java69
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java48
-rw-r--r--proto/src/metrics_constants.proto6
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java2
-rw-r--r--services/backup/java/com/android/server/backup/internal/PerformBackupTask.java52
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java22
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java149
-rw-r--r--services/core/java/com/android/server/am/ActivityStartController.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityStarter.java2
-rw-r--r--services/core/java/com/android/server/am/AppErrorDialog.java31
-rw-r--r--services/core/java/com/android/server/am/AppErrors.java6
-rw-r--r--services/core/java/com/android/server/media/MediaUpdateService.java3
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java13
-rw-r--r--services/core/java/com/android/server/power/Notifier.java51
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java17
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java2
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java10
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java63
-rw-r--r--services/tests/servicestests/AndroidManifest.xml1
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java113
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java28
-rw-r--r--telephony/java/android/telephony/PhoneStateListener.java25
-rw-r--r--telephony/java/android/telephony/PhysicalChannelConfig.aidl20
-rw-r--r--telephony/java/android/telephony/PhysicalChannelConfig.java127
-rw-r--r--tests/FrameworkPerf/AndroidManifest.xml1
-rw-r--r--tests/OneMedia/AndroidManifest.xml1
-rw-r--r--tests/notification/Android.mk2
-rw-r--r--tools/stats_log_api_gen/main.cpp31
-rw-r--r--wifi/java/android/net/wifi/IWifiManager.aidl32
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java28
135 files changed, 5660 insertions, 609 deletions
diff --git a/api/current.txt b/api/current.txt
index b117d3e1624f..e85beab85e72 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -72,6 +72,7 @@ package android {
field public static final java.lang.String DUMP = "android.permission.DUMP";
field public static final java.lang.String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR";
field public static final java.lang.String FACTORY_TEST = "android.permission.FACTORY_TEST";
+ field public static final java.lang.String FOREGROUND_SERVICE = "android.permission.FOREGROUND_SERVICE";
field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED";
field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
@@ -3764,6 +3765,7 @@ package android.app {
method public final void requestShowKeyboardShortcuts();
method public deprecated boolean requestVisibleBehind(boolean);
method public final boolean requestWindowFeature(int);
+ method public final <T extends android.view.View> T requireViewById(int);
method public final void runOnUiThread(java.lang.Runnable);
method public void setActionBar(android.widget.Toolbar);
method public void setContentTransitionManager(android.transition.TransitionManager);
@@ -4458,6 +4460,7 @@ package android.app {
method public void openOptionsMenu();
method public void registerForContextMenu(android.view.View);
method public final boolean requestWindowFeature(int);
+ method public final <T extends android.view.View> T requireViewById(int);
method public void setCancelMessage(android.os.Message);
method public void setCancelable(boolean);
method public void setCanceledOnTouchOutside(boolean);
@@ -5693,6 +5696,7 @@ package android.app {
method public final void setInterruptionFilter(int);
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
+ field public static final java.lang.String ACTION_APP_BLOCK_STATE_CHANGED = "android.app.action.APP_BLOCK_STATE_CHANGED";
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
@@ -38454,6 +38458,7 @@ package android.service.dreams {
method public void onWindowFocusChanged(boolean);
method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback);
method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
+ method public final <T extends android.view.View> T requireViewById(int);
method public void setContentView(int);
method public void setContentView(android.view.View);
method public void setContentView(android.view.View, android.view.ViewGroup.LayoutParams);
@@ -46941,6 +46946,7 @@ package android.view {
method public boolean requestRectangleOnScreen(android.graphics.Rect);
method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean);
method public final void requestUnbufferedDispatch(android.view.MotionEvent);
+ method public final <T extends android.view.View> T requireViewById(int);
method public static int resolveSize(int, int);
method public static int resolveSizeAndState(int, int, int);
method public boolean restoreDefaultFocus();
@@ -47976,6 +47982,7 @@ package android.view {
method public abstract boolean performPanelShortcut(int, int, android.view.KeyEvent, int);
method public final void removeOnFrameMetricsAvailableListener(android.view.Window.OnFrameMetricsAvailableListener);
method public boolean requestFeature(int);
+ method public final <T extends android.view.View> T requireViewById(int);
method public abstract void restoreHierarchyState(android.os.Bundle);
method public abstract android.os.Bundle saveHierarchyState();
method public void setAllowEnterTransitionOverlap(boolean);
@@ -50395,6 +50402,7 @@ package android.webkit {
method public android.graphics.Bitmap getFavicon();
method public android.webkit.WebView.HitTestResult getHitTestResult();
method public deprecated java.lang.String[] getHttpAuthUsernamePassword(java.lang.String, java.lang.String);
+ method public android.os.Looper getLooper();
method public java.lang.String getOriginalUrl();
method public int getProgress();
method public boolean getRendererPriorityWaivedWhenNotVisible();
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 7a7a2f617e6a..0eaefb74f582 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -92,10 +92,20 @@ void StatsLogProcessor::onAnomalyAlarmFired(
void StatsLogProcessor::mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const {
std::vector<Field> uidFields;
- findFields(
- event->getFieldValueMap(),
- buildAttributionUidFieldMatcher(event->GetTagId(), Position::ANY),
- &uidFields);
+ if (android::util::kAtomsWithAttributionChain.find(event->GetTagId()) !=
+ android::util::kAtomsWithAttributionChain.end()) {
+ findFields(
+ event->getFieldValueMap(),
+ buildAttributionUidFieldMatcher(event->GetTagId(), Position::ANY),
+ &uidFields);
+ } else if (android::util::kAtomsWithUidField.find(event->GetTagId()) !=
+ android::util::kAtomsWithUidField.end()) {
+ findFields(
+ event->getFieldValueMap(),
+ buildSimpleAtomFieldMatcher(event->GetTagId(), 1 /* uid is always the 1st field. */),
+ &uidFields);
+ }
+
for (size_t i = 0; i < uidFields.size(); ++i) {
DimensionsValue* value = event->findFieldValueOrNull(uidFields[i]);
if (value != nullptr && value->value_case() == DimensionsValue::ValueCase::kValueInt) {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 7f0ebb45e2d7..77b156f81e1a 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -104,7 +104,7 @@ message Atom {
CpuTimePerUidFreq cpu_time_per_uid_freq = 10010;
WifiActivityEnergyInfo wifi_activity_energy_info = 10011;
ModemActivityInfo modem_activity_info = 10012;
- MemoryStat memory_stat = 10013;
+ ProcessMemoryStat process_memory_stat = 10013;
CpuSuspendTime cpu_suspend_time = 10014;
CpuIdleTime cpu_idle_time = 10015;
CpuActiveTime cpu_active_time = 10016;
@@ -1233,12 +1233,12 @@ message ModemActivityInfo {
/*
* Logs the memory stats for a process
*/
-message MemoryStat {
+message ProcessMemoryStat {
// The uid if available. -1 means not available.
optional int32 uid = 1;
- // The app package name.
- optional string pkg_name = 2;
+ // The process name.
+ optional string process_name = 2;
// # of page-faults
optional int64 pgfault = 3;
@@ -1259,19 +1259,20 @@ message LmkEventOccurred {
// The uid if available. -1 means not available.
optional int32 uid = 1;
- // The app package name.
- optional string pkg_name = 2;
+ // The process name.
+ optional string process_name = 2;
// oom adj score.
optional int32 oom_score = 3;
- // Used as start/stop boundaries for the event
- enum State {
- UNKNOWN = 0;
- START = 1;
- END = 2;
- }
- optional State state = 4;
+ // # of page-faults
+ optional int64 pgfault = 4;
+
+ // # of major page-faults
+ optional int64 pgmajfault = 5;
+
+ // RSS+CACHE(+SWAP)
+ optional int64 usage_in_bytes = 6;
}
/*
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index fdfa32eac8ae..3d6984cea97d 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -58,14 +58,14 @@ public:
/**
* Get the timestamp associated with this event.
*/
- uint64_t GetTimestampNs() const { return mTimestampNs; }
+ inline uint64_t GetTimestampNs() const { return mTimestampNs; }
/**
* Get the tag for this event.
*/
- int GetTagId() const { return mTagId; }
+ inline int GetTagId() const { return mTagId; }
- uint32_t GetUid() const {
+ inline uint32_t GetUid() const {
return mLogUid;
}
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index ef272103a1b6..0455f6a210a9 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -112,6 +112,9 @@ void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLog
void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
ProtoOutputStream* protoOutput) {
flushIfNeededLocked(dumpTimeNs);
+ if (mPastBuckets.empty()) {
+ return;
+ }
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 58dd46400a9f..e26fe5649090 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -168,6 +168,9 @@ void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, Stats
void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
ProtoOutputStream* protoOutput) {
flushIfNeededLocked(dumpTimeNs);
+ if (mPastBuckets.empty()) {
+ return;
+ }
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 821d8ea48aef..25c86d0d46f1 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -102,6 +102,9 @@ void EventMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLog
void EventMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
ProtoOutputStream* protoOutput) {
+ if (mProto->size() <= 0) {
+ return;
+ }
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs);
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 17305e3857b6..24dc5b0fba53 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -125,6 +125,9 @@ void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
VLOG("gauge metric %lld report now...", (long long)mMetricId);
flushIfNeededLocked(dumpTimeNs);
+ if (mPastBuckets.empty()) {
+ return;
+ }
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index e98587356857..ae0c673e3490 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -138,6 +138,9 @@ void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
ProtoOutputStream* protoOutput) {
VLOG("metric %lld dump report now...", (long long)mMetricId);
flushIfNeededLocked(dumpTimeNs);
+ if (mPastBuckets.empty()) {
+ return;
+ }
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS);
diff --git a/cmds/uiautomator/instrumentation/Android.mk b/cmds/uiautomator/instrumentation/Android.mk
index ed99f3e14922..e887539b718a 100644
--- a/cmds/uiautomator/instrumentation/Android.mk
+++ b/cmds/uiautomator/instrumentation/Android.mk
@@ -21,7 +21,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-java-files-under, testrunner-src) \
$(call all-java-files-under, ../library/core-src)
-LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base.stubs
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_STATIC_JAVA_LIBRARIES := junit
LOCAL_MODULE := uiautomator-instrumentation
# TODO: change this to 18 when it's available
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 0a5b848e6220..cd029c06b91d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -17,6 +17,7 @@
package android.app;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+
import static java.lang.Character.MIN_VALUE;
import android.annotation.CallSuper;
@@ -2618,6 +2619,7 @@ public class Activity extends ContextThemeWrapper
* @param id the ID to search for
* @return a view with given ID if found, or {@code null} otherwise
* @see View#findViewById(int)
+ * @see Activity#requireViewById(int)
*/
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
@@ -2625,6 +2627,30 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Finds a view that was identified by the {@code android:id} XML attribute that was processed
+ * in {@link #onCreate}, or throws an IllegalArgumentException if the ID is invalid, or there is
+ * no matching view in the hierarchy.
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
+ *
+ * @param id the ID to search for
+ * @return a view with given ID
+ * @see View#requireViewById(int)
+ * @see Activity#findViewById(int)
+ */
+ @NonNull
+ public final <T extends View> T requireViewById(@IdRes int id) {
+ T view = findViewById(id);
+ if (view == null) {
+ throw new IllegalArgumentException("ID does not reference a View inside this Activity");
+ }
+ return view;
+ }
+
+ /**
* Retrieve a reference to this activity's ActionBar.
*
* @return The Activity's ActionBar, or null if it does not have one.
@@ -4671,6 +4697,7 @@ public class Activity extends ContextThemeWrapper
* their launch had come from the original activity.
* @param intent The Intent to start.
* @param options ActivityOptions or null.
+ * @param permissionToken Token received from the system that permits this call to be made.
* @param ignoreTargetSecurity If true, the activity manager will not check whether the
* caller it is doing the start is, is actually allowed to start the target activity.
* If you set this to true, you must set an explicit component in the Intent and do any
@@ -4679,7 +4706,7 @@ public class Activity extends ContextThemeWrapper
* @hide
*/
public void startActivityAsCaller(Intent intent, @Nullable Bundle options,
- boolean ignoreTargetSecurity, int userId) {
+ IBinder permissionToken, boolean ignoreTargetSecurity, int userId) {
if (mParent != null) {
throw new RuntimeException("Can't be called from a child");
}
@@ -4687,7 +4714,7 @@ public class Activity extends ContextThemeWrapper
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivityAsCaller(
this, mMainThread.getApplicationThread(), mToken, this,
- intent, -1, options, ignoreTargetSecurity, userId);
+ intent, -1, options, permissionToken, ignoreTargetSecurity, userId);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, -1, ar.getResultCode(),
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 455458436c2f..b5a941283184 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -443,6 +443,31 @@ public class ActivityManager {
*/
public static final int INTENT_SENDER_FOREGROUND_SERVICE = 5;
+ /**
+ * Extra included on intents that are delegating the call to
+ * ActivityManager#startActivityAsCaller to another app. This token is necessary for that call
+ * to succeed. Type is IBinder.
+ * @hide
+ */
+ public static final String EXTRA_PERMISSION_TOKEN = "android.app.extra.PERMISSION_TOKEN";
+
+ /**
+ * Extra included on intents that contain an EXTRA_INTENT, with options that the contained
+ * intent may want to be started with. Type is Bundle.
+ * TODO: remove once the ChooserActivity moves to systemui
+ * @hide
+ */
+ public static final String EXTRA_OPTIONS = "android.app.extra.OPTIONS";
+
+ /**
+ * Extra included on intents that contain an EXTRA_INTENT, use this boolean value for the
+ * parameter of the same name when starting the contained intent.
+ * TODO: remove once the ChooserActivity moves to systemui
+ * @hide
+ */
+ public static final String EXTRA_IGNORE_TARGET_SECURITY =
+ "android.app.extra.EXTRA_IGNORE_TARGET_SECURITY";
+
/** @hide User operation call: success! */
public static final int USER_OP_SUCCESS = 0;
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index b162cb165fba..2b648ea6937d 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -16,10 +16,6 @@
package android.app;
-import com.android.internal.R;
-import com.android.internal.app.WindowDecorActionBar;
-import com.android.internal.policy.PhoneWindow;
-
import android.annotation.CallSuper;
import android.annotation.DrawableRes;
import android.annotation.IdRes;
@@ -32,8 +28,8 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.DialogInterface;
-import android.content.res.Configuration;
import android.content.pm.ApplicationInfo;
+import android.content.res.Configuration;
import android.content.res.ResourceId;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@@ -62,6 +58,10 @@ 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 com.android.internal.policy.PhoneWindow;
+
import java.lang.ref.WeakReference;
/**
@@ -512,6 +512,7 @@ public class Dialog implements DialogInterface, Window.Callback,
* @param id the ID to search for
* @return a view with given ID if found, or {@code null} otherwise
* @see View#findViewById(int)
+ * @see Dialog#requireViewById(int)
*/
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
@@ -519,6 +520,30 @@ public class Dialog implements DialogInterface, Window.Callback,
}
/**
+ * Finds the first descendant view with the given ID or throws an IllegalArgumentException if
+ * the ID is invalid (< 0), there is no matching view in the hierarchy, or the dialog has not
+ * yet been fully created (for example, via {@link #show()} or {@link #create()}).
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
+ *
+ * @param id the ID to search for
+ * @return a view with given ID
+ * @see View#requireViewById(int)
+ * @see Dialog#findViewById(int)
+ */
+ @NonNull
+ public final <T extends View> T requireViewById(@IdRes int id) {
+ T view = findViewById(id);
+ if (view == null) {
+ throw new IllegalArgumentException("ID does not reference a View inside this Dialog");
+ }
+ return view;
+ }
+
+ /**
* Set the screen content from a layout resource. The resource will be
* inflated, adding all top-level views to the screen.
*
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 04ee77d764aa..5f5d834425b6 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -438,10 +438,11 @@ interface IActivityManager {
boolean isTopOfTask(in IBinder token);
void notifyLaunchTaskBehindComplete(in IBinder token);
void notifyEnterAnimationComplete(in IBinder token);
+ IBinder requestStartActivityPermissionToken(in IBinder delegatorToken);
int startActivityAsCaller(in IApplicationThread caller, in String callingPackage,
in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho,
int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options,
- boolean ignoreTargetSecurity, int userId);
+ in IBinder permissionToken, boolean ignoreTargetSecurity, int userId);
int addAppTask(in IBinder activityToken, in Intent intent,
in ActivityManager.TaskDescription description, in Bitmap thumbnail);
Point getAppTaskThumbnailSize();
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index c5a58f2eef30..3c38a4ec5fe4 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1874,8 +1874,8 @@ public class Instrumentation {
*/
public ActivityResult execStartActivityAsCaller(
Context who, IBinder contextThread, IBinder token, Activity target,
- Intent intent, int requestCode, Bundle options, boolean ignoreTargetSecurity,
- int userId) {
+ Intent intent, int requestCode, Bundle options, IBinder permissionToken,
+ boolean ignoreTargetSecurity, int userId) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
synchronized (mSync) {
@@ -1906,7 +1906,8 @@ public class Instrumentation {
.startActivityAsCaller(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
- requestCode, 0, null, options, ignoreTargetSecurity, userId);
+ requestCode, 0, null, options, permissionToken,
+ ignoreTargetSecurity, userId);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 45bed5d5711c..49c03ab9cfac 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -93,6 +93,18 @@ public class NotificationManager {
private static boolean localLOGV = false;
/**
+ * Intent that is broadcast when an application is blocked or unblocked.
+ *
+ * This broadcast is only sent to the app whose block state has changed.
+ *
+ * Input: nothing
+ * Output: nothing
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_APP_BLOCK_STATE_CHANGED =
+ "android.app.action.APP_BLOCK_STATE_CHANGED";
+
+ /**
* Intent that is broadcast when a {@link NotificationChannel} is blocked
* (when {@link NotificationChannel#getImportance()} is {@link #IMPORTANCE_NONE}) or unblocked
* (when {@link NotificationChannel#getImportance()} is anything other than
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 256c47934dc5..ea0fd75bec90 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -471,14 +471,6 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
* {@link #onStart} and returns either {@link #START_STICKY}
* or {@link #START_STICKY_COMPATIBILITY}.
*
- * <p>If you need your application to run on platform versions prior to API
- * level 5, you can use the following model to handle the older {@link #onStart}
- * callback in that case. The <code>handleCommand</code> method is implemented by
- * you as appropriate:
- *
- * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java
- * start_compatibility}
- *
* <p class="caution">Note that the system calls this on your
* service's main thread. A service's main thread is the same
* thread where UI operations take place for Activities running in the
@@ -687,6 +679,10 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
* {@link #startService(Intent)} first to tell the system it should keep the service running,
* and then use this method to tell it to keep it running harder.</p>
*
+ * <p>Apps targeting API {@link android.os.Build.VERSION_CODES#P} or later must request
+ * the permission {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use
+ * this API.</p>
+ *
* @param id The identifier for this notification as per
* {@link NotificationManager#notify(int, Notification)
* NotificationManager.notify(int, Notification)}; must not be 0.
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 52870b3f28ae..95e7fe059bb9 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -7037,14 +7037,14 @@ public class DevicePolicyManager {
* task. From {@link android.os.Build.VERSION_CODES#M} removing packages from the lock task
* package list results in locked tasks belonging to those packages to be finished.
* <p>
- * This function can only be called by the device owner or by a profile owner of a user/profile
- * that is affiliated with the device. See {@link #isAffiliatedUser}. Any packages
- * set via this method will be cleared if the user becomes unaffiliated.
+ * This function can only be called by the device owner, a profile owner of an affiliated user
+ * or profile, or the profile owner when no device owner is set. See {@link #isAffiliatedUser}.
+ * Any package set via this method will be cleared if the user becomes unaffiliated.
*
* @param packages The list of packages allowed to enter lock task mode
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
- * an affiliated user or profile.
+ * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
+ * affiliated user or profile, or the profile owner when no device owner is set.
* @see #isAffiliatedUser
* @see Activity#startLockTask()
* @see DeviceAdminReceiver#onLockTaskModeEntering(Context, Intent, String)
@@ -7066,8 +7066,8 @@ public class DevicePolicyManager {
/**
* Returns the list of packages allowed to start the lock task mode.
*
- * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
- * an affiliated user or profile.
+ * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
+ * affiliated user or profile, or the profile owner when no device owner is set.
* @see #isAffiliatedUser
* @see #setLockTaskPackages
*/
@@ -7107,9 +7107,9 @@ public class DevicePolicyManager {
* is in LockTask mode. If this method is not called, none of the features listed here will be
* enabled.
* <p>
- * This function can only be called by the device owner or by a profile owner of a user/profile
- * that is affiliated with the device. See {@link #isAffiliatedUser}. Any features
- * set via this method will be cleared if the user becomes unaffiliated.
+ * This function can only be called by the device owner, a profile owner of an affiliated user
+ * or profile, or the profile owner when no device owner is set. See {@link #isAffiliatedUser}.
+ * Any features set via this method will be cleared if the user becomes unaffiliated.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param flags Bitfield of feature flags:
@@ -7120,9 +7120,10 @@ public class DevicePolicyManager {
* {@link #LOCK_TASK_FEATURE_RECENTS},
* {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS},
* {@link #LOCK_TASK_FEATURE_KEYGUARD}
- * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
- * an affiliated user or profile.
+ * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
+ * affiliated user or profile, or the profile owner when no device owner is set.
* @see #isAffiliatedUser
+ * @throws SecurityException if {@code admin} is not the device owner or the profile owner.
*/
public void setLockTaskFeatures(@NonNull ComponentName admin, @LockTaskFeature int flags) {
throwIfParentInstance("setLockTaskFeatures");
@@ -7140,8 +7141,8 @@ public class DevicePolicyManager {
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @return bitfield of flags. See {@link #setLockTaskFeatures(ComponentName, int)} for a list.
- * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
- * an affiliated user or profile.
+ * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
+ * affiliated user or profile, or the profile owner when no device owner is set.
* @see #isAffiliatedUser
* @see #setLockTaskFeatures
*/
diff --git a/core/java/android/app/backup/BackupManagerMonitor.java b/core/java/android/app/backup/BackupManagerMonitor.java
index ae4a98a4e06e..a91aded1abf6 100644
--- a/core/java/android/app/backup/BackupManagerMonitor.java
+++ b/core/java/android/app/backup/BackupManagerMonitor.java
@@ -172,6 +172,12 @@ public class BackupManagerMonitor {
public static final int LOG_EVENT_ID_NO_PACKAGES = 49;
public static final int LOG_EVENT_ID_TRANSPORT_IS_NULL = 50;
+ /**
+ * The transport returned {@link BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED}.
+ * @hide
+ */
+ public static final int LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = 51;
+
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index 3558e3430470..266f58df3d7f 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -51,6 +51,20 @@ public class BackupTransport {
public static final int AGENT_UNKNOWN = -1004;
public static final int TRANSPORT_QUOTA_EXCEEDED = -1005;
+ /**
+ * Indicates that the transport cannot accept a diff backup for this package.
+ *
+ * <p>Backup manager should clear its state for this package and immediately retry a
+ * non-incremental backup. This might be used if the transport no longer has data for this
+ * package in its backing store.
+ *
+ * <p>This is only valid when backup manager called {@link
+ * #performBackup(PackageInfo, ParcelFileDescriptor, int)} with {@link #FLAG_INCREMENTAL}.
+ *
+ * @hide
+ */
+ public static final int TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = -1006;
+
// Indicates that operation was initiated by user, not a scheduled one.
// Transport should ignore its own moratoriums for call with this flag set.
public static final int FLAG_USER_INITIATED = 1;
@@ -252,6 +266,13 @@ public class BackupTransport {
* set then {@link BackupTransport#FLAG_NON_INCREMENTAL} will be set. Before P neither flag will
* be set regardless of whether the backup is incremental or not.
*
+ * <p>If {@link BackupTransport#FLAG_INCREMENTAL} is set and the transport does not have data
+ * for this package in its storage backend then it cannot apply the incremental diff. Thus it
+ * should return {@link BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED} to indicate
+ * that backup manager should delete its state and retry the package as a non-incremental
+ * backup. Before P, or if this is a non-incremental backup, then this return code is equivalent
+ * to {@link BackupTransport#TRANSPORT_ERROR}.
+ *
* @param packageInfo The identity of the application whose data is being backed up.
* This specifically includes the signature list for the package.
* @param inFd Descriptor of file with data that resulted from invoking the application's
@@ -262,9 +283,11 @@ public class BackupTransport {
* @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far),
* {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this
* specific package, but allow others to proceed),
- * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure), or
- * {@link BackupTransport#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has
- * become lost due to inactivity purge or some other reason and needs re-initializing)
+ * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure), {@link
+ * BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED} (if the transport cannot accept
+ * an incremental backup for this package), or {@link
+ * BackupTransport#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has become lost due to
+ * inactivity purge or some other reason and needs re-initializing)
*/
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) {
return performBackup(packageInfo, inFd);
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 48f56847e88d..fc7886191898 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -894,6 +894,14 @@ public class Build {
/**
* P.
+ *
+ * <p>Applications targeting this or a later release will get these
+ * new changes in behavior:</p>
+ * <ul>
+ * <li>{@link android.app.Service#startForeground Service.startForeground} requires
+ * that apps hold the permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE}.</li>
+ * </ul>
*/
public static final int P = CUR_DEVELOPMENT; // STOPSHIP Replace with the real version.
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4b9f5894e0bc..daf6bd571932 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7602,6 +7602,13 @@ public final class Settings {
public static final String BACKUP_MANAGER_CONSTANTS = "backup_manager_constants";
/**
+ * Flag to set if the system should predictively attempt to re-enable Bluetooth while
+ * the user is driving.
+ * @hide
+ */
+ public static final String BLUETOOTH_ON_WHILE_DRIVING = "bluetooth_on_while_driving";
+
+ /**
* This are the settings to be backed up.
*
* NOTE: Settings are backed up and restored in the order they appear
@@ -11906,6 +11913,19 @@ public final class Settings {
* @hide
*/
public static final String SHOW_FIRST_CRASH_DIALOG = "show_first_crash_dialog";
+
+ /**
+ * If nonzero, crash dialogs will show an option to restart the app.
+ * @hide
+ */
+ public static final String SHOW_RESTART_IN_CRASH_DIALOG = "show_restart_in_crash_dialog";
+
+ /**
+ * If nonzero, crash dialogs will show an option to mute all future crash dialogs for
+ * this app.
+ * @hide
+ */
+ public static final String SHOW_MUTE_IN_CRASH_DIALOG = "show_mute_in_crash_dialog";
}
/**
diff --git a/core/java/android/provider/SettingsValidators.java b/core/java/android/provider/SettingsValidators.java
index 84c9e8867c44..5885b6b50abd 100644
--- a/core/java/android/provider/SettingsValidators.java
+++ b/core/java/android/provider/SettingsValidators.java
@@ -100,7 +100,7 @@ public class SettingsValidators {
String[] subparts = value.split("\\.");
boolean isValidPackageName = true;
for (String subpart : subparts) {
- isValidPackageName |= isSubpartValidForPackageName(subpart);
+ isValidPackageName &= isSubpartValidForPackageName(subpart);
if (!isValidPackageName) break;
}
return isValidPackageName;
@@ -110,7 +110,7 @@ public class SettingsValidators {
if (subpart.length() == 0) return false;
boolean isValidSubpart = Character.isLetter(subpart.charAt(0));
for (int i = 1; i < subpart.length(); i++) {
- isValidSubpart |= (Character.isLetterOrDigit(subpart.charAt(i))
+ isValidSubpart &= (Character.isLetterOrDigit(subpart.charAt(i))
|| (subpart.charAt(i) == '_'));
if (!isValidSubpart) break;
}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 2a245d046486..99e2c620fa03 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -17,6 +17,7 @@ package android.service.dreams;
import android.annotation.IdRes;
import android.annotation.LayoutRes;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -54,7 +55,6 @@ import com.android.internal.util.DumpUtils.Dump;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.List;
/**
* Extend this class to implement a custom dream (available to the user as a "Daydream").
@@ -458,8 +458,16 @@ public class DreamService extends Service implements Window.Callback {
* was processed in {@link #onCreate}.
*
* <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p>
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
*
+ * @param id the ID to search for
* @return The view if found or null otherwise.
+ * @see View#findViewById(int)
+ * @see DreamService#requireViewById(int)
*/
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
@@ -467,6 +475,33 @@ public class DreamService extends Service implements Window.Callback {
}
/**
+ * Finds a view that was identified by the id attribute from the XML that was processed in
+ * {@link #onCreate}, or throws an IllegalArgumentException if the ID is invalid or there is no
+ * matching view in the hierarchy.
+ *
+ * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p>
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
+ *
+ * @param id the ID to search for
+ * @return a view with given ID
+ * @see View#requireViewById(int)
+ * @see DreamService#findViewById(int)
+ */
+ @NonNull
+ public final <T extends View> T requireViewById(@IdRes int id) {
+ T view = findViewById(id);
+ if (view == null) {
+ throw new IllegalArgumentException(
+ "ID does not reference a View inside this DreamService");
+ }
+ return view;
+ }
+
+ /**
* Marks this dream as interactive to receive input events.
*
* <p>Non-interactive dreams (default) will dismiss on the first input event.</p>
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index b313514c8686..25a177edd27c 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -46,6 +46,7 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put("settings_zone_picker_v2", "true");
DEFAULT_FLAGS.put("settings_suggestion_ui_v2", "false");
DEFAULT_FLAGS.put("settings_about_phone_v2", "false");
+ DEFAULT_FLAGS.put("settings_bluetooth_while_driving", "false");
}
/**
diff --git a/core/java/android/util/apk/ApkVerityBuilder.java b/core/java/android/util/apk/ApkVerityBuilder.java
index a0d5e4c2dd8e..5880c6a2d99b 100644
--- a/core/java/android/util/apk/ApkVerityBuilder.java
+++ b/core/java/android/util/apk/ApkVerityBuilder.java
@@ -68,31 +68,78 @@ abstract class ApkVerityBuilder {
static ApkVerityResult generateApkVerity(RandomAccessFile apk,
SignatureInfo signatureInfo, ByteBufferFactory bufferFactory)
throws IOException, SecurityException, NoSuchAlgorithmException, DigestException {
- assertSigningBlockAlignedAndHasFullPages(signatureInfo);
-
long signingBlockSize =
signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset;
long dataSize = apk.length() - signingBlockSize - ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
int[] levelOffset = calculateVerityLevelOffset(dataSize);
+
ByteBuffer output = bufferFactory.create(
CHUNK_SIZE_BYTES + // fsverity header + extensions + padding
- levelOffset[levelOffset.length - 1] + // Merkle tree size
- FSVERITY_HEADER_SIZE_BYTES); // second fsverity header (verbatim copy)
+ levelOffset[levelOffset.length - 1]); // Merkle tree size
+ output.order(ByteOrder.LITTLE_ENDIAN);
- // Start generating the tree from the block boundary as the kernel will expect.
- ByteBuffer treeOutput = slice(output, CHUNK_SIZE_BYTES,
- output.limit() - FSVERITY_HEADER_SIZE_BYTES);
- byte[] rootHash = generateApkVerityTree(apk, signatureInfo, DEFAULT_SALT, levelOffset,
- treeOutput);
+ ByteBuffer header = slice(output, 0, FSVERITY_HEADER_SIZE_BYTES);
+ ByteBuffer extensions = slice(output, FSVERITY_HEADER_SIZE_BYTES, CHUNK_SIZE_BYTES);
+ ByteBuffer tree = slice(output, CHUNK_SIZE_BYTES, output.limit());
+ byte[] apkDigestBytes = new byte[DIGEST_SIZE_BYTES];
+ ByteBuffer apkDigest = ByteBuffer.wrap(apkDigestBytes);
+ apkDigest.order(ByteOrder.LITTLE_ENDIAN);
- ByteBuffer integrityHeader = generateFsverityHeader(apk.length(), DEFAULT_SALT);
- output.put(integrityHeader);
- output.put(generateFsverityExtensions());
+ calculateFsveritySignatureInternal(apk, signatureInfo, tree, apkDigest, header, extensions);
- integrityHeader.rewind();
- output.put(integrityHeader);
output.rewind();
- return new ApkVerityResult(output, rootHash);
+ return new ApkVerityResult(output, apkDigestBytes);
+ }
+
+ /**
+ * Calculates the fsverity root hash for integrity measurement. This needs to be consistent to
+ * what kernel returns.
+ */
+ static byte[] generateFsverityRootHash(RandomAccessFile apk, ByteBuffer apkDigest,
+ SignatureInfo signatureInfo)
+ throws NoSuchAlgorithmException, DigestException, IOException {
+ ByteBuffer verityBlock = ByteBuffer.allocate(CHUNK_SIZE_BYTES)
+ .order(ByteOrder.LITTLE_ENDIAN);
+ ByteBuffer header = slice(verityBlock, 0, FSVERITY_HEADER_SIZE_BYTES);
+ ByteBuffer extensions = slice(verityBlock, FSVERITY_HEADER_SIZE_BYTES, CHUNK_SIZE_BYTES);
+
+ calculateFsveritySignatureInternal(apk, signatureInfo, null, null, header, extensions);
+
+ MessageDigest md = MessageDigest.getInstance(JCA_DIGEST_ALGORITHM);
+ md.update(DEFAULT_SALT);
+ md.update(verityBlock);
+ md.update(apkDigest);
+ return md.digest();
+ }
+
+ private static void calculateFsveritySignatureInternal(
+ RandomAccessFile apk, SignatureInfo signatureInfo, ByteBuffer treeOutput,
+ ByteBuffer rootHashOutput, ByteBuffer headerOutput, ByteBuffer extensionsOutput)
+ throws IOException, NoSuchAlgorithmException, DigestException {
+ assertSigningBlockAlignedAndHasFullPages(signatureInfo);
+
+ long signingBlockSize =
+ signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset;
+ long dataSize = apk.length() - signingBlockSize - ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
+ int[] levelOffset = calculateVerityLevelOffset(dataSize);
+
+ if (treeOutput != null) {
+ byte[] apkRootHash = generateApkVerityTree(apk, signatureInfo, DEFAULT_SALT,
+ levelOffset, treeOutput);
+ if (rootHashOutput != null) {
+ rootHashOutput.put(apkRootHash);
+ }
+ }
+
+ if (headerOutput != null) {
+ generateFsverityHeader(headerOutput, apk.length(), levelOffset.length - 1,
+ DEFAULT_SALT);
+ }
+
+ if (extensionsOutput != null) {
+ generateFsverityExtensions(extensionsOutput, signatureInfo.apkSigningBlockOffset,
+ signingBlockSize, signatureInfo.eocdOffset);
+ }
}
/**
@@ -211,7 +258,7 @@ abstract class ApkVerityBuilder {
eocdCdOffsetFieldPosition - signatureInfo.centralDirOffset),
MMAP_REGION_SIZE_BYTES);
- // 3. Fill up the rest of buffer with 0s.
+ // 3. Consume offset of Signing Block as an alternative EoCD.
ByteBuffer alternativeCentralDirOffset = ByteBuffer.allocate(
ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE).order(ByteOrder.LITTLE_ENDIAN);
alternativeCentralDirOffset.putInt(Math.toIntExact(signatureInfo.apkSigningBlockOffset));
@@ -259,36 +306,109 @@ abstract class ApkVerityBuilder {
return rootHash;
}
- private static ByteBuffer generateFsverityHeader(long fileSize, byte[] salt) {
+ private static ByteBuffer generateFsverityHeader(ByteBuffer buffer, long fileSize, int depth,
+ byte[] salt) {
if (salt.length != 8) {
throw new IllegalArgumentException("salt is not 8 bytes long");
}
- ByteBuffer buffer = ByteBuffer.allocate(FSVERITY_HEADER_SIZE_BYTES);
- buffer.order(ByteOrder.LITTLE_ENDIAN);
-
- // TODO(b/30972906): insert a reference when there is a public one.
+ // TODO(b/30972906): update the reference when there is a better one in public.
buffer.put("TrueBrew".getBytes()); // magic
+
buffer.put((byte) 1); // major version
buffer.put((byte) 0); // minor version
- buffer.put((byte) 12); // log2(block-size) == log2(4096)
- buffer.put((byte) 7); // log2(leaves-per-node) == log2(block-size / digest-size)
- // == log2(4096 / 32)
- buffer.putShort((short) 1); // meta algorithm, 1: SHA-256 FIXME finalize constant
- buffer.putShort((short) 1); // data algorithm, 1: SHA-256 FIXME finalize constant
- buffer.putInt(0x1); // flags, 0x1: has extension, FIXME also hide it
+ buffer.put((byte) 12); // log2(block-size): log2(4096)
+ buffer.put((byte) 7); // log2(leaves-per-node): log2(4096 / 32)
+
+ buffer.putShort((short) 1); // meta algorithm, SHA256_MODE == 1
+ buffer.putShort((short) 1); // data algorithm, SHA256_MODE == 1
+
+ buffer.putInt(0x1); // flags, 0x1: has extension
buffer.putInt(0); // reserved
- buffer.putLong(fileSize); // original i_size
- buffer.put(salt); // salt (8 bytes)
- // TODO(b/30972906): Add extension.
+ buffer.putLong(fileSize); // original file size
+
+ buffer.put((byte) 0); // auth block offset, disabled here
+ buffer.put(salt); // salt (8 bytes)
+ // skip(buffer, 22); // reserved
buffer.rewind();
return buffer;
}
- private static ByteBuffer generateFsverityExtensions() {
- return ByteBuffer.allocate(64); // TODO(b/30972906): implement this.
+ private static ByteBuffer generateFsverityExtensions(ByteBuffer buffer, long signingBlockOffset,
+ long signingBlockSize, long eocdOffset) {
+ // Snapshot of the FSVerity structs (subject to change once upstreamed).
+ //
+ // struct fsverity_header_extension {
+ // u8 extension_count;
+ // u8 reserved[7];
+ // };
+ //
+ // struct fsverity_extension {
+ // __le16 length;
+ // u8 type;
+ // u8 reserved[5];
+ // };
+ //
+ // struct fsverity_extension_elide {
+ // __le64 offset;
+ // __le64 length;
+ // }
+ //
+ // struct fsverity_extension_patch {
+ // __le64 offset;
+ // u8 length;
+ // u8 reserved[7];
+ // u8 databytes[];
+ // };
+
+ // struct fsverity_header_extension
+ buffer.put((byte) 2); // extension count
+ skip(buffer, 3); // reserved
+
+ final int kSizeOfFsverityExtensionHeader = 8;
+
+ {
+ // struct fsverity_extension #1
+ final int kSizeOfFsverityElidedExtension = 16;
+
+ buffer.putShort((short) // total size of extension, padded to 64-bit alignment
+ (kSizeOfFsverityExtensionHeader + kSizeOfFsverityElidedExtension));
+ buffer.put((byte) 0); // ID of elide extension
+ skip(buffer, 5); // reserved
+
+ // struct fsverity_extension_elide
+ buffer.putLong(signingBlockOffset);
+ buffer.putLong(signingBlockSize);
+ }
+
+ {
+ // struct fsverity_extension #2
+ final int kSizeOfFsverityPatchExtension =
+ 8 + // offset size
+ 1 + // size of length from offset (up to 255)
+ 7 + // reserved
+ ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
+ final int kPadding = (int) divideRoundup(kSizeOfFsverityPatchExtension % 8, 8);
+
+ buffer.putShort((short) // total size of extension, padded to 64-bit alignment
+ (kSizeOfFsverityExtensionHeader + kSizeOfFsverityPatchExtension + kPadding));
+ buffer.put((byte) 1); // ID of patch extension
+ skip(buffer, 5); // reserved
+
+ // struct fsverity_extension_patch
+ buffer.putLong(eocdOffset); // offset
+ buffer.put((byte) ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE); // length
+ skip(buffer, 7); // reserved
+ buffer.putInt(Math.toIntExact(signingBlockOffset)); // databytes
+
+ // There are extra kPadding bytes of 0s here, included in the total size field of the
+ // extension header. The output ByteBuffer is assumed to be initialized to 0.
+ }
+
+ buffer.rewind();
+ return buffer;
}
/**
@@ -344,6 +464,11 @@ abstract class ApkVerityBuilder {
return b.slice();
}
+ /** Skip the {@code ByteBuffer} position by {@code bytes}. */
+ private static void skip(ByteBuffer buffer, int bytes) {
+ buffer.position(buffer.position() + bytes);
+ }
+
/** Divides a number and round up to the closest integer. */
private static long divideRoundup(long dividend, long divisor) {
return (dividend + divisor - 1) / divisor;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 05770c357526..a2ecfc469182 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -22209,7 +22209,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @param id the ID to search for
* @return a view with given ID if found, or {@code null} otherwise
- * @see View#findViewById(int)
+ * @see View#requireViewById(int)
*/
@Nullable
public final <T extends View> T findViewById(@IdRes int id) {
@@ -22220,6 +22220,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Finds the first descendant view with the given ID, the view itself if the ID matches
+ * {@link #getId()}, or throws an IllegalArgumentException if the ID is invalid or there is no
+ * matching view in the hierarchy.
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
+ *
+ * @param id the ID to search for
+ * @return a view with given ID
+ * @see View#findViewById(int)
+ */
+ @NonNull
+ public final <T extends View> T requireViewById(@IdRes int id) {
+ T view = findViewById(id);
+ if (view == null) {
+ throw new IllegalArgumentException("ID does not reference a View inside this View");
+ }
+ return view;
+ }
+
+ /**
* Finds a view by its unuque and stable accessibility id.
*
* @param accessibilityId The searched accessibility id.
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 95abea14b1dd..5bd0782d6056 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1339,9 +1339,9 @@ public abstract class Window {
/**
* Finds a view that was identified by the {@code android:id} XML attribute
- * that was processed in {@link android.app.Activity#onCreate}. This will
- * implicitly call {@link #getDecorView} with all of the associated
- * side-effects.
+ * that was processed in {@link android.app.Activity#onCreate}.
+ * <p>
+ * This will implicitly call {@link #getDecorView} with all of the associated side-effects.
* <p>
* <strong>Note:</strong> In most cases -- depending on compiler support --
* the resulting view is automatically cast to the target class type. If
@@ -1351,11 +1351,35 @@ public abstract class Window {
* @param id the ID to search for
* @return a view with given ID if found, or {@code null} otherwise
* @see View#findViewById(int)
+ * @see Window#requireViewById(int)
*/
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
+ /**
+ * Finds a view that was identified by the {@code android:id} XML attribute
+ * that was processed in {@link android.app.Activity#onCreate}, or throws an
+ * IllegalArgumentException if the ID is invalid, or there is no matching view in the hierarchy.
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
+ *
+ * @param id the ID to search for
+ * @return a view with given ID
+ * @see View#requireViewById(int)
+ * @see Window#findViewById(int)
+ */
+ @NonNull
+ public final <T extends View> T requireViewById(@IdRes int id) {
+ T view = findViewById(id);
+ if (view == null) {
+ throw new IllegalArgumentException("ID does not reference a View inside this Window");
+ }
+ return view;
+ }
/**
* Convenience for
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 00860a42a546..2c51ee940039 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -663,6 +663,10 @@ public class WebView extends AbsoluteLayout
if (context == null) {
throw new IllegalArgumentException("Invalid context argument");
}
+ if (mWebViewThread == null) {
+ throw new RuntimeException(
+ "WebView cannot be initialized on a thread that has no Looper.");
+ }
sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=
Build.VERSION_CODES.JELLY_BEAN_MR2;
checkThread();
@@ -2422,6 +2426,14 @@ public class WebView extends AbsoluteLayout
return getFactory().getWebViewClassLoader();
}
+ /**
+ * Returns the {@link Looper} corresponding to the thread on which WebView calls must be made.
+ */
+ @NonNull
+ public Looper getLooper() {
+ return mWebViewThread;
+ }
+
//-------------------------------------------------------------------------
// Interface for WebView providers
//-------------------------------------------------------------------------
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 6e0ba3413e8c..997d47fe8cf0 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -841,7 +841,7 @@ public class ChooserActivity extends ResolverActivity {
}
@Override
- public boolean startAsCaller(Activity activity, Bundle options, int userId) {
+ public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
final Intent intent = getBaseIntentToSend();
if (intent == null) {
return false;
@@ -860,8 +860,7 @@ public class ChooserActivity extends ResolverActivity {
final boolean ignoreTargetSecurity = mSourceInfo != null
&& mSourceInfo.getResolvedComponentName().getPackageName()
.equals(mChooserTarget.getComponentName().getPackageName());
- activity.startActivityAsCaller(intent, options, ignoreTargetSecurity, userId);
- return true;
+ return activity.startAsCallerImpl(intent, options, ignoreTargetSecurity, userId);
}
@Override
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 398d08791b5c..86731bcb4bf6 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -107,7 +107,7 @@ public class IntentForwarderActivity extends Activity {
|| ChooserActivity.class.getName().equals(ri.activityInfo.name));
try {
- startActivityAsCaller(newIntent, null, false, targetUserId);
+ startActivityAsCaller(newIntent, null, null, false, targetUserId);
} catch (RuntimeException e) {
int launchedFromUid = -1;
String launchedFromPackage = "?";
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index ceb06f511108..d6d44908a15b 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -43,6 +43,7 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.PatternMatcher;
import android.os.RemoteException;
import android.os.StrictMode;
@@ -857,6 +858,36 @@ public class ResolverActivity extends Activity {
}
}
+ public boolean startAsCallerImpl(Intent intent, Bundle options, boolean ignoreTargetSecurity,
+ int userId) {
+ // Pass intent to delegate chooser activity with permission token.
+ // TODO: This should move to a trampoline Activity in the system when the ChooserActivity
+ // moves into systemui
+ try {
+ // TODO: Once this is a small springboard activity, it can move off the UI process
+ // and we can move the request method to ActivityManagerInternal.
+ IBinder permissionToken = ActivityManager.getService()
+ .requestStartActivityPermissionToken(getActivityToken());
+ final Intent chooserIntent = new Intent();
+ final ComponentName delegateActivity = ComponentName.unflattenFromString(
+ Resources.getSystem().getString(R.string.config_chooserActivity));
+ chooserIntent.setClassName(delegateActivity.getPackageName(),
+ delegateActivity.getClassName());
+ chooserIntent.putExtra(ActivityManager.EXTRA_PERMISSION_TOKEN, permissionToken);
+
+ // TODO: These extras will change as chooser activity moves into systemui
+ chooserIntent.putExtra(Intent.EXTRA_INTENT, intent);
+ chooserIntent.putExtra(ActivityManager.EXTRA_OPTIONS, options);
+ chooserIntent.putExtra(ActivityManager.EXTRA_IGNORE_TARGET_SECURITY,
+ ignoreTargetSecurity);
+ chooserIntent.putExtra(Intent.EXTRA_USER_ID, userId);
+ startActivity(chooserIntent);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ return true;
+ }
+
public void onActivityStarted(TargetInfo cti) {
// Do nothing
}
@@ -1181,9 +1212,8 @@ public class ResolverActivity extends Activity {
}
@Override
- public boolean startAsCaller(Activity activity, Bundle options, int userId) {
- activity.startActivityAsCaller(mResolvedIntent, options, false, userId);
- return true;
+ public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
+ return activity.startAsCallerImpl(mResolvedIntent, options, false, userId);
}
@Override
@@ -1242,7 +1272,7 @@ public class ResolverActivity extends Activity {
* @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller
* @return true if the start completed successfully
*/
- boolean startAsCaller(Activity activity, Bundle options, int userId);
+ boolean startAsCaller(ResolverActivity activity, Bundle options, int userId);
/**
* Start the activity referenced by this target as a given user.
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index f5af80a29ed1..ebb5f9f9e446 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -35,6 +35,8 @@ oneway interface IStatusBar
void animateCollapsePanels();
void togglePanel();
+ void showChargingAnimation(int batteryLevel);
+
/**
* Notifies the status bar of a System UI visibility flag change.
*
diff --git a/core/jni/android/graphics/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp
index ec15cce1f1d5..8b3ce663fae6 100644
--- a/core/jni/android/graphics/AnimatedImageDrawable.cpp
+++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp
@@ -18,6 +18,7 @@
#include "ImageDecoder.h"
#include "core_jni_helpers.h"
+#include <hwui/AnimatedImageDrawable.h>
#include <hwui/Canvas.h>
#include <SkAndroidCodec.h>
#include <SkAnimatedImage.h>
@@ -27,10 +28,6 @@
using namespace android;
-struct AnimatedImageDrawable {
- sk_sp<SkAnimatedImage> mDrawable;
- SkPaint mPaint;
-};
// Note: jpostProcess holds a handle to the ImageDecoder.
static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/,
@@ -65,20 +62,22 @@ static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/,
picture = recorder.finishRecordingAsPicture();
}
- std::unique_ptr<AnimatedImageDrawable> drawable(new AnimatedImageDrawable);
- drawable->mDrawable = SkAnimatedImage::Make(std::move(imageDecoder->mCodec),
- scaledSize, subset, std::move(picture));
- if (!drawable->mDrawable) {
+
+ sk_sp<SkAnimatedImage> animatedImg = SkAnimatedImage::Make(std::move(imageDecoder->mCodec),
+ scaledSize, subset,
+ std::move(picture));
+ if (!animatedImg) {
doThrowIOE(env, "Failed to create drawable");
return 0;
}
- drawable->mDrawable->start();
+ sk_sp<AnimatedImageDrawable> drawable(new AnimatedImageDrawable(animatedImg));
+ drawable->start();
return reinterpret_cast<jlong>(drawable.release());
}
static void AnimatedImageDrawable_destruct(AnimatedImageDrawable* drawable) {
- delete drawable;
+ SkSafeUnref(drawable);
}
static jlong AnimatedImageDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject /*clazz*/) {
@@ -86,45 +85,43 @@ static jlong AnimatedImageDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject
}
static jlong AnimatedImageDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
- jlong canvasPtr, jlong msecs) {
+ jlong canvasPtr) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
- double timeToNextUpdate = drawable->mDrawable->update(msecs);
auto* canvas = reinterpret_cast<Canvas*>(canvasPtr);
- canvas->drawAnimatedImage(drawable->mDrawable.get(), 0, 0, &drawable->mPaint);
- return (jlong) timeToNextUpdate;
+ return (jlong) canvas->drawAnimatedImage(drawable);
}
static void AnimatedImageDrawable_nSetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
jint alpha) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
- drawable->mPaint.setAlpha(alpha);
+ drawable->setStagingAlpha(alpha);
}
static jlong AnimatedImageDrawable_nGetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
- return drawable->mPaint.getAlpha();
+ return drawable->getStagingAlpha();
}
static void AnimatedImageDrawable_nSetColorFilter(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
jlong nativeFilter) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
auto* filter = reinterpret_cast<SkColorFilter*>(nativeFilter);
- drawable->mPaint.setColorFilter(sk_ref_sp(filter));
+ drawable->setStagingColorFilter(sk_ref_sp(filter));
}
static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
- return drawable->mDrawable->isRunning();
+ return drawable->isRunning();
}
static void AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
- drawable->mDrawable->start();
+ drawable->start();
}
static void AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
- drawable->mDrawable->stop();
+ drawable->stop();
}
static long AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
@@ -136,7 +133,7 @@ static long AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/
static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
{ "nCreate", "(JLandroid/graphics/ImageDecoder;IILandroid/graphics/Rect;)J", (void*) AnimatedImageDrawable_nCreate },
{ "nGetNativeFinalizer", "()J", (void*) AnimatedImageDrawable_nGetNativeFinalizer },
- { "nDraw", "(JJJ)J", (void*) AnimatedImageDrawable_nDraw },
+ { "nDraw", "(JJ)J", (void*) AnimatedImageDrawable_nDraw },
{ "nSetAlpha", "(JI)V", (void*) AnimatedImageDrawable_nSetAlpha },
{ "nGetAlpha", "(J)I", (void*) AnimatedImageDrawable_nGetAlpha },
{ "nSetColorFilter", "(JJ)V", (void*) AnimatedImageDrawable_nSetColorFilter },
diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto
index 95eb889a3f3a..fd2832276922 100644
--- a/core/proto/android/providers/settings.proto
+++ b/core/proto/android/providers/settings.proto
@@ -392,8 +392,10 @@ message GlobalSettingsProto {
optional SettingProto enable_smart_replies_in_notifications = 348;
optional SettingProto show_first_crash_dialog = 349;
optional SettingProto wifi_connected_mac_randomization_enabled = 350;
+ optional SettingProto show_restart_in_crash_dialog = 351;
+ optional SettingProto show_mute_in_crash_dialog = 352;
- // Next tag = 351;
+ // Next tag = 353;
}
message SecureSettingsProto {
@@ -596,8 +598,9 @@ message SecureSettingsProto {
optional SettingProto lockdown_in_power_menu = 194;
optional SettingProto backup_manager_constants = 169;
optional SettingProto show_first_crash_dialog_dev_option = 195;
+ optional SettingProto bluetooth_on_while_driving = 196;
- // Next tag = 196
+ // Next tag = 197
}
message SystemSettingsProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 081c92cebb98..b04680877f89 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -514,6 +514,7 @@
<protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
<protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED" />
<protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED" />
+ <protected-broadcast android:name="android.app.action.APP_BLOCK_STATE_CHANGED" />
<protected-broadcast android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" />
<protected-broadcast android:name="android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS" />
@@ -1933,6 +1934,12 @@
<permission android:name="android.permission.START_ANY_ACTIVITY"
android:protectionLevel="signature" />
+ <!-- Allows an application to start an activity as another app, provided that app has been
+ granted a permissionToken from the ActivityManagerService.
+ @hide -->
+ <permission android:name="android.permission.START_ACTIVITY_AS_CALLER"
+ android:protectionLevel="signature" />
+
<!-- @deprecated The {@link android.app.ActivityManager#restartPackage}
API is no longer supported. -->
<permission android:name="android.permission.RESTART_PACKAGES"
@@ -3710,6 +3717,15 @@
<permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"
android:protectionLevel="signature|development|instant|appop" />
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground}.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE"
+ android:description="@string/permdesc_foregroundService"
+ android:label="@string/permlab_foregroundService"
+ android:protectionLevel="normal|instant" />
+
<!-- @hide Allows system components to access all app shortcuts. -->
<permission android:name="android.permission.ACCESS_SHORTCUTS"
android:protectionLevel="signature" />
diff --git a/core/res/res/drawable/ic_info_outline_24.xml b/core/res/res/drawable/ic_info_outline_24.xml
new file mode 100644
index 000000000000..abba8cf788e6
--- /dev/null
+++ b/core/res/res/drawable/ic_info_outline_24.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2018 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="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
+</vector>
diff --git a/core/res/res/layout/app_error_dialog.xml b/core/res/res/layout/app_error_dialog.xml
index d78ce59872ff..c3b149a1e295 100644
--- a/core/res/res/layout/app_error_dialog.xml
+++ b/core/res/res/layout/app_error_dialog.xml
@@ -18,48 +18,50 @@
*/
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingTop="@dimen/aerr_padding_list_top"
+ android:paddingBottom="@dimen/aerr_padding_list_bottom">
+
+ <Button
+ android:id="@+id/aerr_restart"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
- android:paddingTop="@dimen/aerr_padding_list_top"
- android:paddingBottom="@dimen/aerr_padding_list_bottom">
-
+ android:text="@string/aerr_restart"
+ android:drawableStart="@drawable/ic_refresh"
+ style="@style/aerr_list_item" />
<Button
- android:id="@+id/aerr_restart"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/aerr_restart"
- android:drawableStart="@drawable/ic_refresh"
- style="@style/aerr_list_item"
- />
+ android:id="@+id/aerr_app_info"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/app_info"
+ android:drawableStart="@drawable/ic_info_outline_24"
+ style="@style/aerr_list_item" />
<Button
- android:id="@+id/aerr_close"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/aerr_close_app"
- android:drawableStart="@drawable/ic_close"
- style="@style/aerr_list_item"
- />
+ android:id="@+id/aerr_close"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_close_app"
+ android:drawableStart="@drawable/ic_close"
+ style="@style/aerr_list_item" />
<Button
- android:id="@+id/aerr_report"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/aerr_report"
- android:drawableStart="@drawable/ic_feedback"
- style="@style/aerr_list_item"
- />
+ android:id="@+id/aerr_report"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_report"
+ android:drawableStart="@drawable/ic_feedback"
+ style="@style/aerr_list_item" />
<Button
- android:id="@+id/aerr_mute"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/aerr_mute"
- android:drawableStart="@drawable/ic_eject_24dp"
- style="@style/aerr_list_item"
- />
-
+ android:id="@+id/aerr_mute"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_mute"
+ android:drawableStart="@drawable/ic_eject_24dp"
+ style="@style/aerr_list_item" />
</LinearLayout>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c623c9aa0fb7..7e5a735236cd 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2283,7 +2283,10 @@
Can be customized for other product types -->
<string name="config_chooseTypeAndAccountActivity" translatable="false"
>android/android.accounts.ChooseTypeAndAccountActivity</string>
-
+ <!-- Name of the activity that will handle requests to the system to choose an activity for
+ the purposes of resolving an intent. -->
+ <string name="config_chooserActivity" translatable="false"
+ >com.android.systemui/com.android.systemui.chooser.ChooserActivity</string>
<!-- Component name of a custom ResolverActivity (Intent resolver) to be used instead of
the default framework version. If left empty, then the framework version will be used.
Example: com.google.android.myapp/.resolver.MyResolverActivity -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4119cdcf4c5e..71e963a5bf9e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -916,6 +916,11 @@
<string name="permdesc_persistentActivity" product="default">Allows the app to make parts of itself persistent in memory. This can limit memory available to other apps slowing down the phone.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundService">run foreground service</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundService">Allows the app to make use of foreground services.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_getPackageSize">measure app storage space</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_getPackageSize">Allows the app to retrieve its code, data, and cache sizes</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1711ec97b6e9..ee208734a49d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1067,6 +1067,7 @@
<java-symbol type="string" name="owner_name" />
<java-symbol type="string" name="config_chooseAccountActivity" />
<java-symbol type="string" name="config_chooseTypeAndAccountActivity" />
+ <java-symbol type="string" name="config_chooserActivity" />
<java-symbol type="string" name="config_customResolverActivity" />
<java-symbol type="string" name="config_appsAuthorizedForSharedAccounts" />
<java-symbol type="string" name="error_message_title" />
@@ -2652,6 +2653,7 @@
<java-symbol type="id" name="aerr_report" />
<java-symbol type="id" name="aerr_restart" />
<java-symbol type="id" name="aerr_close" />
+ <java-symbol type="id" name="aerr_app_info" />
<java-symbol type="id" name="aerr_mute" />
<java-symbol type="string" name="status_bar_rotate" />
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index e0947723f502..3e380104fa99 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -51,6 +51,7 @@
<uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
<uses-permission android:name="android.permission.DELETE_CACHE_FILES" />
<uses-permission android:name="android.permission.DOWNLOAD_CACHE_NON_PURGEABLE" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.INJECT_EVENTS" />
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 09ac1d899eee..08d023d03fc4 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -332,7 +332,9 @@ public class SettingsBackupTest {
Settings.Global.SETUP_PREPAID_DETECTION_TARGET_URL,
Settings.Global.SHORTCUT_MANAGER_CONSTANTS,
Settings.Global.SHOW_FIRST_CRASH_DIALOG,
+ Settings.Global.SHOW_MUTE_IN_CRASH_DIALOG,
Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS,
+ Settings.Global.SHOW_RESTART_IN_CRASH_DIALOG,
Settings.Global.SHOW_TEMPERATURE_WARNING,
Settings.Global.SMART_SELECTION_UPDATE_CONTENT_URL,
Settings.Global.SMART_SELECTION_UPDATE_METADATA_URL,
@@ -538,7 +540,8 @@ public class SettingsBackupTest {
Settings.Secure.BACKUP_MANAGER_CONSTANTS,
Settings.Secure.KEYGUARD_SLICE_URI,
Settings.Secure.PARENTAL_CONTROL_ENABLED,
- Settings.Secure.PARENTAL_CONTROL_REDIRECT_URL);
+ Settings.Secure.PARENTAL_CONTROL_REDIRECT_URL,
+ Settings.Secure.BLUETOOTH_ON_WHILE_DRIVING);
@Test
public void systemSettingsBackedUpOrBlacklisted() {
diff --git a/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java b/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java
index 4c4aeaf49855..e7507667b17f 100644
--- a/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java
@@ -17,6 +17,8 @@
package android.provider;
import static org.junit.Assert.fail;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import android.platform.test.annotations.Presubmit;
import android.provider.SettingsValidators.Validator;
@@ -28,13 +30,101 @@ import org.junit.runner.RunWith;
import java.util.Map;
-/** Tests that ensure all backed up settings have non-null validators. */
+/**
+ * Tests that ensure all backed up settings have non-null validators. Also, common validators
+ * are tested.
+ */
@Presubmit
@RunWith(AndroidJUnit4.class)
@SmallTest
public class SettingsValidatorsTest {
@Test
+ public void testNonNegativeIntegerValidator() {
+ assertTrue(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("1"));
+ assertTrue(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("0"));
+ assertFalse(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("-1"));
+ assertFalse(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("rectangle"));
+ }
+
+ @Test
+ public void testAnyIntegerValidator() {
+ assertTrue(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("1"));
+ assertTrue(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("0"));
+ assertTrue(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("-1"));
+ assertFalse(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("rectangle"));
+ }
+
+ @Test
+ public void testComponentNameValidator() {
+ assertTrue(SettingsValidators.COMPONENT_NAME_VALIDATOR.validate(
+ "android/com.android.internal.backup.LocalTransport"));
+ assertFalse(SettingsValidators.COMPONENT_NAME_VALIDATOR.validate("rectangle"));
+ }
+
+ @Test
+ public void testLocaleValidator() {
+ assertTrue(SettingsValidators.LOCALE_VALIDATOR.validate("en_US"));
+ assertTrue(SettingsValidators.LOCALE_VALIDATOR.validate("es"));
+ assertFalse(SettingsValidators.LOCALE_VALIDATOR.validate("rectangle"));
+ }
+
+ @Test
+ public void testPackageNameValidator() {
+ assertTrue(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate(
+ "com.google.android"));
+ assertFalse(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate("com.google.@android"));
+ assertFalse(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate(".com.google.android"));
+ assertFalse(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate(".com.google.5android"));
+ }
+
+ @Test
+ public void testDiscreteValueValidator() {
+ String[] beerTypes = new String[]{"Ale", "American IPA", "Stout"};
+ Validator v = new SettingsValidators.DiscreteValueValidator(beerTypes);
+ assertTrue(v.validate("Ale"));
+ assertTrue(v.validate("American IPA"));
+ assertTrue(v.validate("Stout"));
+ assertFalse(v.validate("Cider")); // just juice pretending to be beer
+ }
+
+ @Test
+ public void testInclusiveIntegerRangeValidator() {
+ Validator v = new SettingsValidators.InclusiveIntegerRangeValidator(0, 5);
+ assertTrue(v.validate("0"));
+ assertTrue(v.validate("2"));
+ assertTrue(v.validate("5"));
+ assertFalse(v.validate("-1"));
+ assertFalse(v.validate("6"));
+ }
+
+ @Test
+ public void testInclusiveFloatRangeValidator() {
+ Validator v = new SettingsValidators.InclusiveFloatRangeValidator(0.0f, 5.0f);
+ assertTrue(v.validate("0.0"));
+ assertTrue(v.validate("2.0"));
+ assertTrue(v.validate("5.0"));
+ assertFalse(v.validate("-1.0"));
+ assertFalse(v.validate("6.0"));
+ }
+
+ @Test
+ public void testComponentNameListValidator() {
+ Validator v = new SettingsValidators.ComponentNameListValidator(",");
+ assertTrue(v.validate("android/com.android.internal.backup.LocalTransport,"
+ + "com.google.android.gms/.backup.migrate.service.D2dTransport"));
+ assertFalse(v.validate("com.google.5android,android"));
+ }
+
+ @Test
+ public void testPackageNameListValidator() {
+ Validator v = new SettingsValidators.PackageNameListValidator(",");
+ assertTrue(v.validate("com.android.internal.backup.LocalTransport,com.google.android.gms"));
+ assertFalse(v.validate("5com.android.internal.backup.LocalTransport,android"));
+ }
+
+
+ @Test
public void ensureAllBackedUpSystemSettingsHaveValidators() {
String offenders = getOffenders(concat(Settings.System.SETTINGS_TO_BACKUP,
Settings.System.LEGACY_RESTORE_SETTINGS), Settings.System.VALIDATORS);
diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
index b18fa747557d..c0bc3a8eeb9e 100644
--- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
@@ -24,6 +24,7 @@ import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.UserHandle;
import android.os.UserManager;
import android.support.test.InstrumentationRegistry;
@@ -269,8 +270,8 @@ public class IntentForwarderActivityTest {
}
@Override
- public void startActivityAsCaller(Intent intent, @Nullable Bundle options, boolean
- ignoreTargetSecurity, int userId) {
+ public void startActivityAsCaller(Intent intent, @Nullable Bundle options,
+ IBinder permissionToken, boolean ignoreTargetSecurity, int userId) {
mStartActivityIntent = intent;
mUserIdActivityLaunchedIn = userId;
}
@@ -293,4 +294,4 @@ public class IntentForwarderActivityTest {
return mPm;
}
}
-} \ No newline at end of file
+}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index c0958cd6cdd7..0949a90877e1 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -368,6 +368,7 @@ applications that come with the platform
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/>
+ <permission name="android.permission.START_ACTIVITY_AS_CALLER"/>
<permission name="android.permission.START_TASKS_FROM_RECENTS"/>
<permission name="android.permission.STATUS_BAR"/>
<permission name="android.permission.STOP_APP_SWITCHES"/>
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index da170c0fae24..6d3ddd5c2c28 100644
--- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -118,9 +118,12 @@ public class AnimatedImageDrawable extends Drawable implements Animatable {
@Override
public void draw(@NonNull Canvas canvas) {
- long nextUpdate = nDraw(mNativePtr, canvas.getNativeCanvasWrapper(),
- SystemClock.uptimeMillis());
- scheduleSelf(mRunnable, nextUpdate);
+ long nextUpdate = nDraw(mNativePtr, canvas.getNativeCanvasWrapper());
+ // a value <= 0 indicates that the drawable is stopped or that renderThread
+ // will manage the animation
+ if (nextUpdate > 0) {
+ scheduleSelf(mRunnable, nextUpdate);
+ }
}
@Override
@@ -130,6 +133,7 @@ public class AnimatedImageDrawable extends Drawable implements Animatable {
+ " 255! provided " + alpha);
}
nSetAlpha(mNativePtr, alpha);
+ invalidateSelf();
}
@Override
@@ -141,6 +145,7 @@ public class AnimatedImageDrawable extends Drawable implements Animatable {
public void setColorFilter(@Nullable ColorFilter colorFilter) {
long nativeFilter = colorFilter == null ? 0 : colorFilter.getNativeInstance();
nSetColorFilter(mNativePtr, nativeFilter);
+ invalidateSelf();
}
@Override
@@ -161,7 +166,10 @@ public class AnimatedImageDrawable extends Drawable implements Animatable {
@Override
public void start() {
- nStart(mNativePtr);
+ if (isRunning() == false) {
+ nStart(mNativePtr);
+ invalidateSelf();
+ }
}
@Override
@@ -173,7 +181,7 @@ public class AnimatedImageDrawable extends Drawable implements Animatable {
@Nullable ImageDecoder decoder, int width, int height, Rect cropRect)
throws IOException;
private static native long nGetNativeFinalizer();
- private static native long nDraw(long nativePtr, long canvasNativePtr, long msecs);
+ private static native long nDraw(long nativePtr, long canvasNativePtr);
private static native void nSetAlpha(long nativePtr, int alpha);
private static native int nGetAlpha(long nativePtr);
private static native void nSetColorFilter(long nativePtr, long nativeFilter);
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index 3a5f7b73e196..e3740e3cf284 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -27,7 +27,6 @@ import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
-import android.graphics.ImageDecoder;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Outline;
@@ -50,7 +49,6 @@ import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -113,7 +111,7 @@ public class BitmapDrawable extends Drawable {
*/
@Deprecated
public BitmapDrawable() {
- init(new BitmapState((Bitmap) null), null);
+ mBitmapState = new BitmapState((Bitmap) null);
}
/**
@@ -126,7 +124,8 @@ public class BitmapDrawable extends Drawable {
@SuppressWarnings("unused")
@Deprecated
public BitmapDrawable(Resources res) {
- init(new BitmapState((Bitmap) null), res);
+ mBitmapState = new BitmapState((Bitmap) null);
+ mBitmapState.mTargetDensity = mTargetDensity;
}
/**
@@ -136,7 +135,7 @@ public class BitmapDrawable extends Drawable {
*/
@Deprecated
public BitmapDrawable(Bitmap bitmap) {
- init(new BitmapState(bitmap), null);
+ this(new BitmapState(bitmap), null);
}
/**
@@ -144,7 +143,8 @@ public class BitmapDrawable extends Drawable {
* the display metrics of the resources.
*/
public BitmapDrawable(Resources res, Bitmap bitmap) {
- init(new BitmapState(bitmap), res);
+ this(new BitmapState(bitmap), res);
+ mBitmapState.mTargetDensity = mTargetDensity;
}
/**
@@ -154,7 +154,10 @@ public class BitmapDrawable extends Drawable {
*/
@Deprecated
public BitmapDrawable(String filepath) {
- this(null, filepath);
+ this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
+ if (mBitmapState.mBitmap == null) {
+ android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
+ }
}
/**
@@ -162,21 +165,10 @@ public class BitmapDrawable extends Drawable {
*/
@SuppressWarnings("unused")
public BitmapDrawable(Resources res, String filepath) {
- Bitmap bitmap = null;
- try (FileInputStream stream = new FileInputStream(filepath)) {
- bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, stream),
- (decoder, info, src) -> {
- decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
- });
- } catch (Exception e) {
- /* do nothing. This matches the behavior of BitmapFactory.decodeFile()
- If the exception happened on decode, mBitmapState.mBitmap will be null.
- */
- } finally {
- init(new BitmapState(bitmap), res);
- if (mBitmapState.mBitmap == null) {
- android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
- }
+ this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
+ mBitmapState.mTargetDensity = mTargetDensity;
+ if (mBitmapState.mBitmap == null) {
+ android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
}
}
@@ -187,7 +179,10 @@ public class BitmapDrawable extends Drawable {
*/
@Deprecated
public BitmapDrawable(java.io.InputStream is) {
- this(null, is);
+ this(new BitmapState(BitmapFactory.decodeStream(is)), null);
+ if (mBitmapState.mBitmap == null) {
+ android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
+ }
}
/**
@@ -195,21 +190,10 @@ public class BitmapDrawable extends Drawable {
*/
@SuppressWarnings("unused")
public BitmapDrawable(Resources res, java.io.InputStream is) {
- Bitmap bitmap = null;
- try {
- bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, is),
- (decoder, info, src) -> {
- decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
- });
- } catch (Exception e) {
- /* do nothing. This matches the behavior of BitmapFactory.decodeStream()
- If the exception happened on decode, mBitmapState.mBitmap will be null.
- */
- } finally {
- init(new BitmapState(bitmap), res);
- if (mBitmapState.mBitmap == null) {
- android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
- }
+ this(new BitmapState(BitmapFactory.decodeStream(is)), null);
+ mBitmapState.mTargetDensity = mTargetDensity;
+ if (mBitmapState.mBitmap == null) {
+ android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
}
}
@@ -828,19 +812,9 @@ public class BitmapDrawable extends Drawable {
}
}
- int density = Bitmap.DENSITY_NONE;
- if (value.density == TypedValue.DENSITY_DEFAULT) {
- density = DisplayMetrics.DENSITY_DEFAULT;
- } else if (value.density != TypedValue.DENSITY_NONE) {
- density = value.density;
- }
-
Bitmap bitmap = null;
try (InputStream is = r.openRawResource(srcResId, value)) {
- ImageDecoder.Source source = ImageDecoder.createSource(r, is, density);
- bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, src) -> {
- decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
- });
+ bitmap = BitmapFactory.decodeResourceStream(r, value, is, null, null);
} catch (Exception e) {
// Do nothing and pick up the error below.
}
@@ -1039,21 +1013,14 @@ public class BitmapDrawable extends Drawable {
}
}
- private BitmapDrawable(BitmapState state, Resources res) {
- init(state, res);
- }
-
/**
- * The one helper to rule them all. This is called by all public & private
+ * The one constructor to rule them all. This is called by all public
* constructors to set the state and initialize local properties.
*/
- private void init(BitmapState state, Resources res) {
+ private BitmapDrawable(BitmapState state, Resources res) {
mBitmapState = state;
- updateLocalState(res);
- if (mBitmapState != null && res != null) {
- mBitmapState.mTargetDensity = mTargetDensity;
- }
+ updateLocalState(res);
}
/**
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 36a4d26d62bb..f17cd768c386 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -37,7 +37,6 @@ import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
-import android.graphics.ImageDecoder;
import android.graphics.Insets;
import android.graphics.NinePatch;
import android.graphics.Outline;
@@ -51,13 +50,11 @@ import android.graphics.Xfermode;
import android.os.Trace;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.util.StateSet;
import android.util.TypedValue;
import android.util.Xml;
import android.view.View;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
@@ -1178,10 +1175,6 @@ public abstract class Drawable {
return null;
}
- if (opts == null) {
- return getBitmapDrawable(res, value, is);
- }
-
/* ugh. The decodeStream contract is that we have already allocated
the pad rect, but if the bitmap does not had a ninepatch chunk,
then the pad will be ignored. If we could change this to lazily
@@ -1214,33 +1207,6 @@ public abstract class Drawable {
return null;
}
- private static Drawable getBitmapDrawable(Resources res, TypedValue value, InputStream is) {
- try {
- ImageDecoder.Source source = null;
- if (value != null) {
- int density = Bitmap.DENSITY_NONE;
- if (value.density == TypedValue.DENSITY_DEFAULT) {
- density = DisplayMetrics.DENSITY_DEFAULT;
- } else if (value.density != TypedValue.DENSITY_NONE) {
- density = value.density;
- }
- source = ImageDecoder.createSource(res, is, density);
- } else {
- source = ImageDecoder.createSource(res, is);
- }
-
- return ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
- decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
- });
- } catch (IOException e) {
- /* do nothing.
- If the exception happened on decode, the drawable will be null.
- */
- Log.e("Drawable", "Unable to decode stream: " + e);
- }
- return null;
- }
-
/**
* Create a drawable from an XML document. For more information on how to
* create resources in XML, see
@@ -1340,10 +1306,11 @@ public abstract class Drawable {
}
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, pathName);
- try (FileInputStream stream = new FileInputStream(pathName)) {
- return getBitmapDrawable(null, null, stream);
- } catch(IOException e) {
- // Do nothing; we will just return null if the FileInputStream had an error
+ try {
+ Bitmap bm = BitmapFactory.decodeFile(pathName);
+ if (bm != null) {
+ return drawableFromBitmap(null, bm, null, null, null, pathName);
+ }
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 7cacaf6a16ad..17f9b7cd62fc 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -137,6 +137,7 @@ cc_defaults {
whole_static_libs: ["libskia"],
srcs: [
+ "hwui/AnimatedImageDrawable.cpp",
"hwui/Bitmap.cpp",
"font/CacheTexture.cpp",
"font/Font.cpp",
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index fb7b24623568..e1df1e7725b5 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -495,9 +495,9 @@ void RecordingCanvas::drawNinePatch(Bitmap& bitmap, const android::Res_png_9patc
refPaint(paint), refBitmap(bitmap), refPatch(&patch)));
}
-void RecordingCanvas::drawAnimatedImage(SkAnimatedImage*, float left, float top,
- const SkPaint*) {
+double RecordingCanvas::drawAnimatedImage(AnimatedImageDrawable*) {
// Unimplemented
+ return 0;
}
// Text
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index dd06ada9da3d..e663402a80f3 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -183,8 +183,7 @@ public:
virtual void drawNinePatch(Bitmap& bitmap, const android::Res_png_9patch& chunk, float dstLeft,
float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) override;
- virtual void drawAnimatedImage(SkAnimatedImage*, float left, float top,
- const SkPaint* paint) override;
+ virtual double drawAnimatedImage(AnimatedImageDrawable*) override;
// Text
virtual bool drawTextAbsolutePos() const override { return false; }
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index dc274cf50a52..b2edd3392873 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -725,18 +725,8 @@ void SkiaCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& chunk, floa
mCanvas->drawImageLattice(image.get(), lattice, dst, addFilter(paint, &tmpPaint, colorFilter));
}
-void SkiaCanvas::drawAnimatedImage(SkAnimatedImage* image, float left, float top,
- const SkPaint* paint) {
- sk_sp<SkPicture> pic(image->newPictureSnapshot());
- SkMatrix matrixStorage;
- SkMatrix* matrix;
- if (left == 0.0f && top == 0.0f) {
- matrix = nullptr;
- } else {
- matrixStorage = SkMatrix::MakeTrans(left, top);
- matrix = &matrixStorage;
- }
- mCanvas->drawPicture(pic.get(), matrix, paint);
+double SkiaCanvas::drawAnimatedImage(AnimatedImageDrawable* imgDrawable) {
+ return imgDrawable->drawStaging(mCanvas);
}
void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) {
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 7137210406fb..3efc22a03cdf 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -124,8 +124,7 @@ public:
virtual void drawNinePatch(Bitmap& bitmap, const android::Res_png_9patch& chunk, float dstLeft,
float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) override;
- virtual void drawAnimatedImage(SkAnimatedImage*, float left, float top,
- const SkPaint* paint) override;
+ virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) override;
virtual bool drawTextAbsolutePos() const override { return true; }
virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
new file mode 100644
index 000000000000..36dd06f2fce9
--- /dev/null
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2018 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 "AnimatedImageDrawable.h"
+
+#include "thread/Task.h"
+#include "thread/TaskManager.h"
+#include "thread/TaskProcessor.h"
+#include "utils/TraceUtils.h"
+
+#include <SkPicture.h>
+#include <SkRefCnt.h>
+#include <SkTime.h>
+#include <SkTLazy.h>
+
+namespace android {
+
+AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage)
+ : mSkAnimatedImage(std::move(animatedImage)) { }
+
+void AnimatedImageDrawable::syncProperties() {
+ mAlpha = mStagingAlpha;
+ mColorFilter = mStagingColorFilter;
+}
+
+void AnimatedImageDrawable::start() {
+ SkAutoExclusive lock(mLock);
+
+ mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot());
+
+ mSkAnimatedImage->start();
+}
+
+void AnimatedImageDrawable::stop() {
+ SkAutoExclusive lock(mLock);
+ mSkAnimatedImage->stop();
+ mSnapshot.reset(nullptr);
+}
+
+bool AnimatedImageDrawable::isRunning() {
+ return mSkAnimatedImage->isRunning();
+}
+
+// This is really a Task<void> but that doesn't really work when Future<>
+// expects to be able to get/set a value
+class AnimatedImageDrawable::AnimatedImageTask : public uirenderer::Task<bool> {
+public:
+ AnimatedImageTask(AnimatedImageDrawable* animatedImageDrawable)
+ : mAnimatedImageDrawable(sk_ref_sp(animatedImageDrawable)) {}
+
+ sk_sp<AnimatedImageDrawable> mAnimatedImageDrawable;
+ bool mIsCompleted = false;
+};
+
+class AnimatedImageDrawable::AnimatedImageTaskProcessor : public uirenderer::TaskProcessor<bool> {
+public:
+ explicit AnimatedImageTaskProcessor(uirenderer::TaskManager* taskManager)
+ : uirenderer::TaskProcessor<bool>(taskManager) {}
+ ~AnimatedImageTaskProcessor() {}
+
+ virtual void onProcess(const sp<uirenderer::Task<bool>>& task) override {
+ ATRACE_NAME("Updating AnimatedImageDrawables");
+ AnimatedImageTask* t = static_cast<AnimatedImageTask*>(task.get());
+ t->mAnimatedImageDrawable->update();
+ t->mIsCompleted = true;
+ task->setResult(true);
+ };
+};
+
+void AnimatedImageDrawable::scheduleUpdate(uirenderer::TaskManager* taskManager) {
+ if (!mSkAnimatedImage->isRunning()
+ || (mDecodeTask.get() != nullptr && !mDecodeTask->mIsCompleted)) {
+ return;
+ }
+
+ if (!mDecodeTaskProcessor.get()) {
+ mDecodeTaskProcessor = new AnimatedImageTaskProcessor(taskManager);
+ }
+
+ // TODO get one frame ahead and only schedule updates when you need to replenish
+ mDecodeTask = new AnimatedImageTask(this);
+ mDecodeTaskProcessor->add(mDecodeTask);
+}
+
+void AnimatedImageDrawable::update() {
+ SkAutoExclusive lock(mLock);
+
+ if (!mSkAnimatedImage->isRunning()) {
+ return;
+ }
+
+ const double currentTime = SkTime::GetMSecs();
+ if (currentTime >= mNextFrameTime) {
+ mNextFrameTime = mSkAnimatedImage->update(currentTime);
+ mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot());
+ mIsDirty = true;
+ }
+}
+
+void AnimatedImageDrawable::onDraw(SkCanvas* canvas) {
+ SkTLazy<SkPaint> lazyPaint;
+ if (mAlpha != SK_AlphaOPAQUE || mColorFilter.get()) {
+ lazyPaint.init();
+ lazyPaint.get()->setAlpha(mAlpha);
+ lazyPaint.get()->setColorFilter(mColorFilter);
+ lazyPaint.get()->setFilterQuality(kLow_SkFilterQuality);
+ }
+
+ SkAutoExclusive lock(mLock);
+ if (mSkAnimatedImage->isRunning()) {
+ canvas->drawPicture(mSnapshot, nullptr, lazyPaint.getMaybeNull());
+ } else {
+ // TODO: we could potentially keep the cached surface around if there is a paint and we know
+ // the drawable is attached to the view system
+ SkAutoCanvasRestore acr(canvas, false);
+ if (lazyPaint.isValid()) {
+ canvas->saveLayer(mSkAnimatedImage->getBounds(), lazyPaint.get());
+ }
+ mSkAnimatedImage->draw(canvas);
+ }
+
+ mIsDirty = false;
+}
+
+double AnimatedImageDrawable::drawStaging(SkCanvas* canvas) {
+ // update the drawable with the current time
+ double nextUpdate = mSkAnimatedImage->update(SkTime::GetMSecs());
+ SkAutoCanvasRestore acr(canvas, false);
+ if (mStagingAlpha != SK_AlphaOPAQUE || mStagingColorFilter.get()) {
+ SkPaint paint;
+ paint.setAlpha(mStagingAlpha);
+ paint.setColorFilter(mStagingColorFilter);
+ canvas->saveLayer(mSkAnimatedImage->getBounds(), &paint);
+ }
+ canvas->drawDrawable(mSkAnimatedImage.get());
+ return nextUpdate;
+}
+
+}; // namespace android
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h
new file mode 100644
index 000000000000..18764afde138
--- /dev/null
+++ b/libs/hwui/hwui/AnimatedImageDrawable.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#pragma once
+
+#include <cutils/compiler.h>
+#include <utils/RefBase.h>
+
+#include <SkAnimatedImage.h>
+#include <SkCanvas.h>
+#include <SkColorFilter.h>
+#include <SkDrawable.h>
+#include <SkMutex.h>
+
+class SkPicture;
+
+namespace android {
+
+namespace uirenderer {
+class TaskManager;
+}
+
+/**
+ * Native component of android.graphics.drawable.AnimatedImageDrawables.java. This class can be
+ * drawn into Canvas.h and maintains the state needed to drive the animation from the RenderThread.
+ */
+class ANDROID_API AnimatedImageDrawable : public SkDrawable {
+public:
+ AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage);
+
+ /**
+ * This returns true if the animation has updated and signals that the next draw will contain
+ * new content.
+ */
+ bool isDirty() const { return mIsDirty; }
+
+ int getStagingAlpha() const { return mStagingAlpha; }
+ void setStagingAlpha(int alpha) { mStagingAlpha = alpha; }
+ void setStagingColorFilter(sk_sp<SkColorFilter> filter) { mStagingColorFilter = filter; }
+ void syncProperties();
+
+ virtual SkRect onGetBounds() override {
+ return mSkAnimatedImage->getBounds();
+ }
+
+ double drawStaging(SkCanvas* canvas);
+
+ void start();
+ void stop();
+ bool isRunning();
+
+ void scheduleUpdate(uirenderer::TaskManager* taskManager);
+
+protected:
+ virtual void onDraw(SkCanvas* canvas) override;
+
+private:
+ void update();
+
+ sk_sp<SkAnimatedImage> mSkAnimatedImage;
+ sk_sp<SkPicture> mSnapshot;
+ SkMutex mLock;
+
+ int mStagingAlpha = SK_AlphaOPAQUE;
+ sk_sp<SkColorFilter> mStagingColorFilter;
+
+ int mAlpha = SK_AlphaOPAQUE;
+ sk_sp<SkColorFilter> mColorFilter;
+ double mNextFrameTime = 0.0;
+ bool mIsDirty = false;
+
+ class AnimatedImageTask;
+ class AnimatedImageTaskProcessor;
+ sp<AnimatedImageTask> mDecodeTask;
+ sp<AnimatedImageTaskProcessor> mDecodeTaskProcessor;
+};
+
+}; // namespace android
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 5efd35764635..cae4542b18e8 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -73,6 +73,7 @@ typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot;
typedef std::function<void(uint16_t* text, float* positions)> ReadGlyphFunc;
+class AnimatedImageDrawable;
class Bitmap;
class Paint;
struct Typeface;
@@ -238,8 +239,7 @@ public:
float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) = 0;
- virtual void drawAnimatedImage(SkAnimatedImage*, float left, float top,
- const SkPaint* paint) = 0;
+ virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) = 0;
/**
* Specifies if the positions passed to ::drawText are absolute or relative
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index cb10901c4ccf..cf0b6a4d1dcc 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -31,6 +31,9 @@ void SkiaDisplayList::syncContents() {
for (auto& functor : mChildFunctors) {
functor.syncFunctor();
}
+ for (auto& animatedImage : mAnimatedImages) {
+ animatedImage->syncProperties();
+ }
for (auto& vectorDrawable : mVectorDrawables) {
vectorDrawable->syncProperties();
}
@@ -89,6 +92,18 @@ bool SkiaDisplayList::prepareListAndChildren(
}
bool isDirty = false;
+ for (auto& animatedImage : mAnimatedImages) {
+ // If any animated image in the display list needs updated, then damage the node.
+ if (animatedImage->isDirty()) {
+ isDirty = true;
+ }
+ if (animatedImage->isRunning()) {
+ static_cast<SkiaPipeline*>(info.canvasContext.getRenderPipeline())
+ ->scheduleDeferredUpdate(animatedImage);
+ info.out.hasAnimations = true;
+ }
+ }
+
for (auto& vectorDrawable : mVectorDrawables) {
// If any vector drawable in the display list needs update, damage the node.
if (vectorDrawable->isDirty()) {
@@ -109,6 +124,7 @@ void SkiaDisplayList::reset() {
mMutableImages.clear();
mVectorDrawables.clear();
+ mAnimatedImages.clear();
mChildFunctors.clear();
mChildNodes.clear();
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 6883d33291ec..818ec114a5b3 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -17,6 +17,7 @@
#pragma once
#include "DisplayList.h"
+#include "hwui/AnimatedImageDrawable.h"
#include "GLFunctorDrawable.h"
#include "RenderNodeDrawable.h"
@@ -144,6 +145,7 @@ public:
std::deque<GLFunctorDrawable> mChildFunctors;
std::vector<SkImage*> mMutableImages;
std::vector<VectorDrawableRoot*> mVectorDrawables;
+ std::vector<AnimatedImageDrawable*> mAnimatedImages;
SkLiteDL mDisplayList;
// mProjectionReceiver points to a child node (stored in mChildNodes) that is as a projection
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 9db39d954e4c..534782a5dc02 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -40,6 +40,7 @@ uint8_t SkiaPipeline::mSpotShadowAlpha = 0;
Vector3 SkiaPipeline::mLightCenter = {FLT_MIN, FLT_MIN, FLT_MIN};
SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) {
+ mAnimatedImageDrawables.reserve(30);
mVectorDrawables.reserve(30);
}
@@ -326,6 +327,15 @@ void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& cli
ATRACE_NAME("flush commands");
surface->getCanvas()->flush();
+
+ // TODO move to another method
+ if (!mAnimatedImageDrawables.empty()) {
+ ATRACE_NAME("Update AnimatedImageDrawables");
+ for (auto animatedImage : mAnimatedImageDrawables) {
+ animatedImage->scheduleUpdate(getTaskManager());
+ }
+ mAnimatedImageDrawables.clear();
+ }
}
namespace {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 27092270bb80..cc75e9c5b38d 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -18,6 +18,7 @@
#include <SkSurface.h>
#include "FrameBuilder.h"
+#include "hwui/AnimatedImageDrawable.h"
#include "renderthread/CanvasContext.h"
#include "renderthread/IRenderPipeline.h"
@@ -54,6 +55,12 @@ public:
std::vector<VectorDrawableRoot*>* getVectorDrawables() { return &mVectorDrawables; }
+ void scheduleDeferredUpdate(AnimatedImageDrawable* imageDrawable) {
+ mAnimatedImageDrawables.push_back(imageDrawable);
+ }
+
+ std::vector<AnimatedImageDrawable*>* getAnimatingImages() { return &mAnimatedImageDrawables; }
+
static void destroyLayer(RenderNode* node);
static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
@@ -137,6 +144,11 @@ private:
*/
std::vector<VectorDrawableRoot*> mVectorDrawables;
+ /**
+ * populated by prepareTree with images with active animations
+ */
+ std::vector<AnimatedImageDrawable*> mAnimatedImageDrawables;
+
// Block of properties used only for debugging to record a SkPicture and save it in a file.
/**
* mCapturedFile is used to enforce we don't capture more than once for a given name (cause
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 035cea3f61b0..eabe2e87fc49 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -246,6 +246,12 @@ void SkiaRecordingCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& ch
}
}
+double SkiaRecordingCanvas::drawAnimatedImage(AnimatedImageDrawable* animatedImage) {
+ drawDrawable(animatedImage);
+ mDisplayList->mAnimatedImages.push_back(animatedImage);
+ return 0;
+}
+
}; // namespace skiapipeline
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index d35bbabb652f..0e5dbdbab078 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -53,6 +53,7 @@ public:
virtual void drawNinePatch(Bitmap& hwuiBitmap, const android::Res_png_9patch& chunk,
float dstLeft, float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) override;
+ virtual double drawAnimatedImage(AnimatedImageDrawable* animatedImage) override;
virtual void drawRoundRect(uirenderer::CanvasPropertyPrimitive* left,
uirenderer::CanvasPropertyPrimitive* top,
diff --git a/media/java/android/media/AudioPort.java b/media/java/android/media/AudioPort.java
index 19bf51d982eb..047db19431c2 100644
--- a/media/java/android/media/AudioPort.java
+++ b/media/java/android/media/AudioPort.java
@@ -20,7 +20,7 @@ package android.media;
* An audio port is a node of the audio framework or hardware that can be connected to or
* disconnect from another audio node to create a specific audio routing configuration.
* Examples of audio ports are an output device (speaker) or an output mix (see AudioMixPort).
- * All attributes that are relevant for applications to make routing selection are decribed
+ * All attributes that are relevant for applications to make routing selection are described
* in an AudioPort, in particular:
* - possible channel mask configurations.
* - audio format (PCM 16bit, PCM 24bit...)
@@ -173,6 +173,7 @@ public class AudioPort {
/**
* Build a specific configuration of this audio port for use by methods
* like AudioManager.connectAudioPatch().
+ * @param samplingRate
* @param channelMask The desired channel mask. AudioFormat.CHANNEL_OUT_DEFAULT if no change
* from active configuration requested.
* @param format The desired audio format. AudioFormat.ENCODING_DEFAULT if no change
diff --git a/media/java/android/media/MediaBrowser2.java b/media/java/android/media/MediaBrowser2.java
index fa0090207749..33377bc69668 100644
--- a/media/java/android/media/MediaBrowser2.java
+++ b/media/java/android/media/MediaBrowser2.java
@@ -16,12 +16,14 @@
package android.media;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.media.update.ApiLoader;
import android.media.update.MediaBrowser2Provider;
import android.os.Bundle;
+import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -35,7 +37,7 @@ public class MediaBrowser2 extends MediaController2 {
/**
* Callback to listen events from {@link MediaLibraryService2}.
*/
- public abstract static class BrowserCallback extends MediaController2.ControllerCallback {
+ public static class BrowserCallback extends MediaController2.ControllerCallback {
/**
* Called with the result of {@link #getBrowserRoot(Bundle)}.
* <p>
@@ -46,8 +48,55 @@ public class MediaBrowser2 extends MediaController2 {
* @param rootMediaId media id of the browser root. Can be {@code null}
* @param rootExtra extra of the browser root. Can be {@code null}
*/
- public abstract void onGetRootResult(Bundle rootHints, @Nullable String rootMediaId,
- @Nullable Bundle rootExtra);
+ public void onGetRootResult(Bundle rootHints, @Nullable String rootMediaId,
+ @Nullable Bundle rootExtra) { }
+
+ /**
+ * Called when the item has been returned by the library service for the previous
+ * {@link MediaBrowser2#getItem} call.
+ * <p>
+ * Result can be null if there had been error.
+ *
+ * @param mediaId media id
+ * @param result result. Can be {@code null}
+ */
+ public void onItemLoaded(@NonNull String mediaId, @Nullable MediaItem2 result) { }
+
+ /**
+ * Called when the list of items has been returned by the library service for the previous
+ * {@link MediaBrowser2#getChildren(String, int, int, Bundle)}.
+ *
+ * @param parentId parent id
+ * @param page page number that you've specified
+ * @param pageSize page size that you've specified
+ * @param options optional bundle that you've specified
+ * @param result result. Can be {@code null}
+ */
+ public void onChildrenLoaded(@NonNull String parentId, int page, int pageSize,
+ @Nullable Bundle options, @Nullable List<MediaItem2> result) { }
+
+ /**
+ * Called when there's change in the parent's children.
+ *
+ * @param parentId parent id that you've specified with subscribe
+ * @param options optional bundle that you've specified with subscribe
+ */
+ public void onChildrenChanged(@NonNull String parentId, @Nullable Bundle options) { }
+
+ /**
+ * Called when the search result has been returned by the library service for the previous
+ * {@link MediaBrowser2#search(String, int, int, Bundle)}.
+ * <p>
+ * Result can be null if there had been error.
+ *
+ * @param query query string that you've specified
+ * @param page page number that you've specified
+ * @param pageSize page size that you've specified
+ * @param options optional bundle that you've specified
+ * @param result result. Can be {@code null}
+ */
+ public void onSearchResult(@NonNull String query, int page, int pageSize,
+ @Nullable Bundle options, @Nullable List<MediaItem2> result) { }
}
public MediaBrowser2(Context context, SessionToken token, BrowserCallback callback,
@@ -66,4 +115,62 @@ public class MediaBrowser2 extends MediaController2 {
public void getBrowserRoot(Bundle rootHints) {
mProvider.getBrowserRoot_impl(rootHints);
}
+
+ /**
+ * Subscribe to a parent id for the change in its children. When there's a change,
+ * {@link BrowserCallback#onChildrenChanged(String, Bundle)} will be called with the bundle
+ * that you've specified. You should call {@link #getChildren(String, int, int, Bundle)} to get
+ * the actual contents for the parent.
+ *
+ * @param parentId parent id
+ * @param options optional bundle
+ */
+ public void subscribe(String parentId, @Nullable Bundle options) {
+ mProvider.subscribe_impl(parentId, options);
+ }
+
+ /**
+ * Unsubscribe for changes to the children of the parent, which was previously subscribed with
+ * {@link #subscribe(String, Bundle)}.
+ *
+ * @param parentId parent id
+ * @param options optional bundle
+ */
+ public void unsubscribe(String parentId, @Nullable Bundle options) {
+ mProvider.unsubscribe_impl(parentId, options);
+ }
+
+ /**
+ * Get the media item with the given media id. Result would be sent back asynchronously with the
+ * {@link BrowserCallback#onItemLoaded(String, MediaItem2)}.
+ *
+ * @param mediaId media id
+ */
+ public void getItem(String mediaId) {
+ mProvider.getItem_impl(mediaId);
+ }
+
+ /**
+ * Get list of children under the parent. Result would be sent back asynchronously with the
+ * {@link BrowserCallback#onChildrenLoaded(String, int, int, Bundle, List)}.
+ *
+ * @param parentId
+ * @param page
+ * @param pageSize
+ * @param options
+ */
+ public void getChildren(String parentId, int page, int pageSize, @Nullable Bundle options) {
+ mProvider.getChildren_impl(parentId, page, pageSize, options);
+ }
+
+ /**
+ *
+ * @param query search query deliminated by string
+ * @param page page number to get search result. Starts from {@code 1}
+ * @param pageSize page size. Should be greater or equal to {@code 1}
+ * @param extras extra bundle
+ */
+ public void search(String query, int page, int pageSize, Bundle extras) {
+ mProvider.search_impl(query, page, pageSize, extras);
+ }
}
diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java
index 3836e789354d..dca102706645 100644
--- a/media/java/android/media/MediaController2.java
+++ b/media/java/android/media/MediaController2.java
@@ -18,16 +18,19 @@ package android.media;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.content.Context;
-import android.media.MediaPlayerBase.PlaybackListener;
+import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.PlaylistParam;
import android.media.session.MediaSessionManager;
-import android.media.session.PlaybackState;
import android.media.update.ApiLoader;
import android.media.update.MediaController2Provider;
-import android.os.Handler;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ResultReceiver;
import java.util.List;
import java.util.concurrent.Executor;
@@ -37,7 +40,7 @@ import java.util.concurrent.Executor;
* {@link MediaSessionService2} in any status. Media buttons and other commands can be sent to
* the session.
* <p>
- * When you're done, use {@link #release()} to clean up resources. This also helps session service
+ * When you're done, use {@link #close()} to clean up resources. This also helps session service
* to be destroyed when there's no controller associated with it.
* <p>
* When controlling {@link MediaSession2}, the controller will be available immediately after
@@ -87,7 +90,7 @@ public class MediaController2 implements AutoCloseable {
public void onDisconnected() { }
/**
- * Called when the session sets the custom layout through the
+ * Called when the session set the custom layout through the
* {@link MediaSession2#setCustomLayout(ControllerInfo, List)}.
* <p>
* Can be called before {@link #onConnected(CommandGroup)} is called.
@@ -95,6 +98,137 @@ public class MediaController2 implements AutoCloseable {
* @param layout
*/
public void onCustomLayoutChanged(List<CommandButton> layout) { }
+
+ /**
+ * Called when the session has changed anything related with the {@link PlaybackInfo}.
+ *
+ * @param info new playback info
+ */
+ public void onAudioInfoChanged(PlaybackInfo info) { }
+
+ /**
+ * Called when the allowed commands are changed by session.
+ *
+ * @param commands newly allowed commands
+ */
+ public void onAllowedCommandsChanged(CommandGroup commands) { }
+
+ /**
+ * Called when the session sent a custom command.
+ *
+ * @param command
+ * @param args
+ * @param receiver
+ */
+ public void onCustomCommand(Command command, @Nullable Bundle args,
+ @Nullable ResultReceiver receiver) { }
+
+ /**
+ * Called when the playlist is changed.
+ *
+ * @param list
+ * @param param
+ */
+ public void onPlaylistChanged(
+ @NonNull List<MediaItem2> list, @NonNull PlaylistParam param) { }
+
+ /**
+ * Called when the playback state is changed.
+ *
+ * @param state
+ */
+ public void onPlaybackStateChanged(@NonNull PlaybackState2 state) { }
+ }
+
+ /**
+ * Holds information about the current playback and how audio is handled for
+ * this session.
+ */
+ // The same as MediaController.PlaybackInfo
+ public static final class PlaybackInfo {
+ /**
+ * The session uses remote playback.
+ */
+ public static final int PLAYBACK_TYPE_REMOTE = 2;
+ /**
+ * The session uses local playback.
+ */
+ public static final int PLAYBACK_TYPE_LOCAL = 1;
+
+ private final int mVolumeType;
+ private final int mVolumeControl;
+ private final int mMaxVolume;
+ private final int mCurrentVolume;
+ private final AudioAttributes mAudioAttrs;
+
+ /**
+ * @hide
+ */
+ public PlaybackInfo(int type, AudioAttributes attrs, int control, int max, int current) {
+ mVolumeType = type;
+ mAudioAttrs = attrs;
+ mVolumeControl = control;
+ mMaxVolume = max;
+ mCurrentVolume = current;
+ }
+
+ /**
+ * Get the type of playback which affects volume handling. One of:
+ * <ul>
+ * <li>{@link #PLAYBACK_TYPE_LOCAL}</li>
+ * <li>{@link #PLAYBACK_TYPE_REMOTE}</li>
+ * </ul>
+ *
+ * @return The type of playback this session is using.
+ */
+ public int getPlaybackType() {
+ return mVolumeType;
+ }
+
+ /**
+ * Get the audio attributes for this session. The attributes will affect
+ * volume handling for the session. When the volume type is
+ * {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} these may be ignored by the
+ * remote volume handler.
+ *
+ * @return The attributes for this session.
+ */
+ public AudioAttributes getAudioAttributes() {
+ return mAudioAttrs;
+ }
+
+ /**
+ * Get the type of volume control that can be used. One of:
+ * <ul>
+ * <li>{@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}</li>
+ * <li>{@link VolumeProvider#VOLUME_CONTROL_RELATIVE}</li>
+ * <li>{@link VolumeProvider#VOLUME_CONTROL_FIXED}</li>
+ * </ul>
+ *
+ * @return The type of volume control that may be used with this
+ * session.
+ */
+ public int getVolumeControl() {
+ return mVolumeControl;
+ }
+
+ /**
+ * Get the maximum volume that may be set for this session.
+ *
+ * @return The maximum allowed volume where this session is playing.
+ */
+ public int getMaxVolume() {
+ return mMaxVolume;
+ }
+
+ /**
+ * Get the current volume for this session.
+ *
+ * @return The current volume where this session is playing.
+ */
+ public int getCurrentVolume() {
+ return mCurrentVolume;
+ }
}
private final MediaController2Provider mProvider;
@@ -147,8 +281,7 @@ public class MediaController2 implements AutoCloseable {
/**
* @return token
*/
- public @NonNull
- SessionToken getSessionToken() {
+ public @NonNull SessionToken getSessionToken() {
return mProvider.getSessionToken_impl();
}
@@ -179,33 +312,304 @@ public class MediaController2 implements AutoCloseable {
mProvider.skipToNext_impl();
}
+ /**
+ * Request that the player prepare its playback. In other words, other sessions can continue
+ * to play during the preparation of this session. This method can be used to speed up the
+ * start of the playback. Once the preparation is done, the session will change its playback
+ * state to {@link PlaybackState2#STATE_PAUSED}. Afterwards, {@link #play} can be called to
+ * start playback.
+ */
+ public void prepare() {
+ mProvider.prepare_impl();
+ }
+
+ /**
+ * Start fast forwarding. If playback is already fast forwarding this
+ * may increase the rate.
+ */
+ public void fastForward() {
+ mProvider.fastForward_impl();
+ }
+
+ /**
+ * Start rewinding. If playback is already rewinding this may increase
+ * the rate.
+ */
+ public void rewind() {
+ mProvider.rewind_impl();
+ }
+
+ /**
+ * Move to a new location in the media stream.
+ *
+ * @param pos Position to move to, in milliseconds.
+ */
+ public void seekTo(long pos) {
+ mProvider.seekTo_impl(pos);
+ }
+
+ /**
+ * Sets the index of current DataSourceDesc in the play list to be played.
+ *
+ * @param index the index of DataSourceDesc in the play list you want to play
+ * @throws IllegalArgumentException if the play list is null
+ * @throws NullPointerException if index is outside play list range
+ */
+ public void setCurrentPlaylistItem(int index) {
+ mProvider.setCurrentPlaylistItem_impl(index);
+ }
+
+ /**
+ * @hide
+ */
+ public void skipForward() {
+ // To match with KEYCODE_MEDIA_SKIP_FORWARD
+ }
- public @Nullable PlaybackState getPlaybackState() {
+ /**
+ * @hide
+ */
+ public void skipBackward() {
+ // To match with KEYCODE_MEDIA_SKIP_BACKWARD
+ }
+
+ /**
+ * Request that the player start playback for a specific media id.
+ *
+ * @param mediaId The id of the requested media.
+ * @param extras Optional extras that can include extra information about the media item
+ * to be played.
+ */
+ public void playFromMediaId(@NonNull String mediaId, @Nullable Bundle extras) {
+ mProvider.playFromMediaId_impl(mediaId, extras);
+ }
+
+ /**
+ * Request that the player start playback for a specific search query.
+ * An empty or null query should be treated as a request to play any
+ * music.
+ *
+ * @param query The search query.
+ * @param extras Optional extras that can include extra information
+ * about the query.
+ */
+ public void playFromSearch(@NonNull String query, @Nullable Bundle extras) {
+ mProvider.playFromSearch_impl(query, extras);
+ }
+
+ /**
+ * Request that the player start playback for a specific {@link Uri}.
+ *
+ * @param uri The URI of the requested media.
+ * @param extras Optional extras that can include extra information about the media item
+ * to be played.
+ */
+ public void playFromUri(@NonNull String uri, @Nullable Bundle extras) {
+ mProvider.playFromUri_impl(uri, extras);
+ }
+
+
+ /**
+ * Request that the player prepare playback for a specific media id. In other words, other
+ * sessions can continue to play during the preparation of this session. This method can be
+ * used to speed up the start of the playback. Once the preparation is done, the session
+ * will change its playback state to {@link PlaybackState2#STATE_PAUSED}. Afterwards,
+ * {@link #play} can be called to start playback. If the preparation is not needed,
+ * {@link #playFromMediaId} can be directly called without this method.
+ *
+ * @param mediaId The id of the requested media.
+ * @param extras Optional extras that can include extra information about the media item
+ * to be prepared.
+ */
+ public void prepareFromMediaId(@NonNull String mediaId, @Nullable Bundle extras) {
+ mProvider.prepareMediaId_impl(mediaId, extras);
+ }
+
+ /**
+ * Request that the player prepare playback for a specific search query. An empty or null
+ * query should be treated as a request to prepare any music. In other words, other sessions
+ * can continue to play during the preparation of this session. This method can be used to
+ * speed up the start of the playback. Once the preparation is done, the session will
+ * change its playback state to {@link PlaybackState2#STATE_PAUSED}. Afterwards,
+ * {@link #play} can be called to start playback. If the preparation is not needed,
+ * {@link #playFromSearch} can be directly called without this method.
+ *
+ * @param query The search query.
+ * @param extras Optional extras that can include extra information
+ * about the query.
+ */
+ public void prepareFromSearch(@NonNull String query, @Nullable Bundle extras) {
+ mProvider.prepareFromSearch_impl(query, extras);
+ }
+
+ /**
+ * Request that the player prepare playback for a specific {@link Uri}. In other words,
+ * other sessions can continue to play during the preparation of this session. This method
+ * can be used to speed up the start of the playback. Once the preparation is done, the
+ * session will change its playback state to {@link PlaybackState2#STATE_PAUSED}. Afterwards,
+ * {@link #play} can be called to start playback. If the preparation is not needed,
+ * {@link #playFromUri} can be directly called without this method.
+ *
+ * @param uri The URI of the requested media.
+ * @param extras Optional extras that can include extra information about the media item
+ * to be prepared.
+ */
+ public void prepareFromUri(@NonNull Uri uri, @Nullable Bundle extras) {
+ mProvider.prepareFromUri_impl(uri, extras);
+ }
+
+ /**
+ * Set the volume of the output this session is playing on. The command will be ignored if it
+ * does not support {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}.
+ * <p>
+ * If the session is local playback, this changes the device's volume with the stream that
+ * session's player is using. Flags will be specified for the {@link AudioManager}.
+ * <p>
+ * If the session is remote player (i.e. session has set volume provider), its volume provider
+ * will receive this request instead.
+ *
+ * @see #getPlaybackInfo()
+ * @param value The value to set it to, between 0 and the reported max.
+ * @param flags flags from {@link AudioManager} to include with the volume request for local
+ * playback
+ */
+ public void setVolumeTo(int value, int flags) {
+ mProvider.setVolumeTo_impl(value, flags);
+ }
+
+ /**
+ * Adjust the volume of the output this session is playing on. The direction
+ * must be one of {@link AudioManager#ADJUST_LOWER},
+ * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
+ * The command will be ignored if the session does not support
+ * {@link VolumeProvider#VOLUME_CONTROL_RELATIVE} or
+ * {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}.
+ * <p>
+ * If the session is local playback, this changes the device's volume with the stream that
+ * session's player is using. Flags will be specified for the {@link AudioManager}.
+ * <p>
+ * If the session is remote player (i.e. session has set volume provider), its volume provider
+ * will receive this request instead.
+ *
+ * @see #getPlaybackInfo()
+ * @param direction The direction to adjust the volume in.
+ * @param flags flags from {@link AudioManager} to include with the volume request for local
+ * playback
+ */
+ public void adjustVolume(int direction, int flags) {
+ mProvider.adjustVolume_impl(direction, flags);
+ }
+
+ /**
+ * Get the rating type supported by the session. One of:
+ * <ul>
+ * <li>{@link Rating2#RATING_NONE}</li>
+ * <li>{@link Rating2#RATING_HEART}</li>
+ * <li>{@link Rating2#RATING_THUMB_UP_DOWN}</li>
+ * <li>{@link Rating2#RATING_3_STARS}</li>
+ * <li>{@link Rating2#RATING_4_STARS}</li>
+ * <li>{@link Rating2#RATING_5_STARS}</li>
+ * <li>{@link Rating2#RATING_PERCENTAGE}</li>
+ * </ul>
+ *
+ * @return The supported rating type
+ */
+ public int getRatingType() {
+ return mProvider.getRatingType_impl();
+ }
+
+ /**
+ * Get an intent for launching UI associated with this session if one exists.
+ *
+ * @return A {@link PendingIntent} to launch UI or null.
+ */
+ public @Nullable PendingIntent getSessionActivity() {
+ return mProvider.getSessionActivity_impl();
+ }
+
+ /**
+ * Get the latest {@link PlaybackState2} from the session.
+ *
+ * @return a playback state
+ */
+ public PlaybackState2 getPlaybackState() {
return mProvider.getPlaybackState_impl();
}
/**
- * Add a {@link PlaybackListener} to listen changes in the
- * {@link MediaSession2}.
+ * Get the current playback info for this session.
+ *
+ * @return The current playback info or null.
+ */
+ public @Nullable PlaybackInfo getPlaybackInfo() {
+ return mProvider.getPlaybackInfo_impl();
+ }
+
+ /**
+ * Rate the current content. This will cause the rating to be set for
+ * the current user. The Rating type must match the type returned by
+ * {@link #getRatingType()}.
+ *
+ * @param rating The rating to set for the current content
+ */
+ public void setRating(Rating2 rating) {
+ mProvider.setRating_impl(rating);
+ }
+
+ /**
+ * Send custom command to the session
+ *
+ * @param command custom command
+ * @param args optional argument
+ * @param cb optional result receiver
+ */
+ public void sendCustomCommand(@NonNull Command command, @Nullable Bundle args,
+ @Nullable ResultReceiver cb) {
+ mProvider.sendCustomCommand_impl(command, args, cb);
+ }
+
+ /**
+ * Return playlist from the session.
+ *
+ * @return playlist. Can be {@code null} if the controller doesn't have enough permission.
+ */
+ public @Nullable List<MediaItem2> getPlaylist() {
+ return mProvider.getPlaylist_impl();
+ }
+
+ public @Nullable PlaylistParam getPlaylistParam() {
+ return mProvider.getPlaylistParam_impl();
+ }
+
+ /**
+ * Removes the media item at index in the play list.
+ *<p>
+ * If index is same as the current index of the playlist, current playback
+ * will be stopped and playback moves to next source in the list.
*
- * @param listener the listener that will be run
- * @param handler the Handler that will receive the listener
- * @throws IllegalArgumentException Called when either the listener or handler is {@code null}.
+ * @return the removed DataSourceDesc at index in the play list
+ * @throws IllegalArgumentException if the play list is null
+ * @throws IndexOutOfBoundsException if index is outside play list range
*/
- // TODO(jaewan): Match with the addSessionAvailabilityListener() that tells the current state
- // through the listener.
- // TODO(jaewan): Can handler be null? Follow the API guideline after it's finalized.
- public void addPlaybackListener(@NonNull PlaybackListener listener, @NonNull Handler handler) {
- mProvider.addPlaybackListener_impl(listener, handler);
+ // TODO(jaewan): Remove with index was previously rejected by council (b/36524925)
+ // TODO(jaewan): Should we also add movePlaylistItem from index to index?
+ public void removePlaylistItem(MediaItem2 item) {
+ mProvider.removePlaylistItem_impl(item);
}
/**
- * Remove previously added {@link PlaybackListener}.
+ * Inserts the media item to the play list at position index.
+ * <p>
+ * This will not change the currently playing media item.
+ * If index is less than or equal to the current index of the play list,
+ * the current index of the play list will be incremented correspondingly.
*
- * @param listener the listener to be removed
- * @throws IllegalArgumentException if the listener is {@code null}.
+ * @param index the index you want to add dsd to the play list
+ * @param item the media item you want to add to the play list
+ * @throws IndexOutOfBoundsException if index is outside play list range
+ * @throws NullPointerException if dsd is null
*/
- public void removePlaybackListener(@NonNull PlaybackListener listener) {
- mProvider.removePlaybackListener_impl(listener);
+ public void addPlaylistItem(int index, MediaItem2 item) {
+ mProvider.addPlaylistItem_impl(index, item);
}
}
diff --git a/media/java/android/media/MediaItem2.java b/media/java/android/media/MediaItem2.java
new file mode 100644
index 000000000000..96a87d5dc68e
--- /dev/null
+++ b/media/java/android/media/MediaItem2.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2018 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;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A class with information on a single media item with the metadata information.
+ * Media item are application dependent so we cannot guarantee that they contain the right values.
+ * <p>
+ * When it's sent to a controller or browser, it's anonymized and data descriptor wouldn't be sent.
+ * <p>
+ * This object isn't a thread safe.
+ *
+ * @hide
+ */
+// TODO(jaewan): Unhide and extends from DataSourceDesc.
+// Note) Feels like an anti-pattern. We should anonymize MediaItem2 to remove *all*
+// information in the DataSourceDesc. Why it should extends from this?
+// TODO(jaewan): Move this to updatable
+// Previously MediaBrowser.MediaItem
+public class MediaItem2 {
+ private final int mFlags;
+ private MediaMetadata2 mMetadata;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE })
+ public @interface Flags { }
+
+ /**
+ * Flag: Indicates that the item has children of its own.
+ */
+ public static final int FLAG_BROWSABLE = 1 << 0;
+
+ /**
+ * Flag: Indicates that the item is playable.
+ * <p>
+ * The id of this item may be passed to
+ * {@link MediaController2#playFromMediaId(String, Bundle)}
+ */
+ public static final int FLAG_PLAYABLE = 1 << 1;
+
+ /**
+ * Create a new media item.
+ *
+ * @param metadata metadata with the media id.
+ * @param flags The flags for this item.
+ */
+ public MediaItem2(@NonNull MediaMetadata2 metadata, @Flags int flags) {
+ mFlags = flags;
+ setMetadata(metadata);
+ }
+
+ /**
+ * Return this object as a bundle to share between processes.
+ *
+ * @return a new bundle instance
+ */
+ public Bundle toBundle() {
+ // TODO(jaewan): Fill here when we rebase.
+ return new Bundle();
+ }
+
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("MediaItem2{");
+ sb.append("mFlags=").append(mFlags);
+ sb.append(", mMetadata=").append(mMetadata);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ /**
+ * Gets the flags of the item.
+ */
+ public @Flags int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Returns whether this item is browsable.
+ * @see #FLAG_BROWSABLE
+ */
+ public boolean isBrowsable() {
+ return (mFlags & FLAG_BROWSABLE) != 0;
+ }
+
+ /**
+ * Returns whether this item is playable.
+ * @see #FLAG_PLAYABLE
+ */
+ public boolean isPlayable() {
+ return (mFlags & FLAG_PLAYABLE) != 0;
+ }
+
+ /**
+ * Set a metadata. Metadata shouldn't be null and should have non-empty media id.
+ *
+ * @param metadata
+ */
+ public void setMetadata(@NonNull MediaMetadata2 metadata) {
+ if (metadata == null) {
+ throw new IllegalArgumentException("metadata cannot be null");
+ }
+ if (TextUtils.isEmpty(metadata.getMediaId())) {
+ throw new IllegalArgumentException("metadata must have a non-empty media id");
+ }
+ mMetadata = metadata;
+ }
+
+ /**
+ * Returns the metadata of the media.
+ */
+ public @NonNull MediaMetadata2 getMetadata() {
+ return mMetadata;
+ }
+
+ /**
+ * Returns the media id in the {@link MediaMetadata2} for this item.
+ * @see MediaMetadata2#METADATA_KEY_MEDIA_ID
+ */
+ public @Nullable String getMediaId() {
+ return mMetadata.getMediaId();
+ }
+}
diff --git a/media/java/android/media/MediaLibraryService2.java b/media/java/android/media/MediaLibraryService2.java
index bbc940799926..b98936e6c870 100644
--- a/media/java/android/media/MediaLibraryService2.java
+++ b/media/java/android/media/MediaLibraryService2.java
@@ -18,14 +18,19 @@ package android.media;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.content.Context;
import android.media.MediaSession2.BuilderBase;
import android.media.MediaSession2.ControllerInfo;
import android.media.update.ApiLoader;
+import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider;
+import android.media.update.MediaSession2Provider;
import android.media.update.MediaSessionService2Provider;
import android.os.Bundle;
import android.service.media.MediaBrowserService.BrowserRoot;
+import java.util.List;
+
/**
* Base class for media library services.
* <p>
@@ -59,14 +64,50 @@ public abstract class MediaLibraryService2 extends MediaSessionService2 {
* Session for the media library service.
*/
public class MediaLibrarySession extends MediaSession2 {
+ private final MediaLibrarySessionProvider mProvider;
+
MediaLibrarySession(Context context, MediaPlayerBase player, String id,
- SessionCallback callback) {
- super(context, player, id, callback);
+ SessionCallback callback, VolumeProvider volumeProvider,
+ int ratingType, PendingIntent sessionActivity) {
+ super(context, player, id, callback, volumeProvider, ratingType, sessionActivity);
+ mProvider = (MediaLibrarySessionProvider) getProvider();
+ }
+
+ @Override
+ MediaSession2Provider createProvider(Context context, MediaPlayerBase player, String id,
+ SessionCallback callback, VolumeProvider volumeProvider, int ratingType,
+ PendingIntent sessionActivity) {
+ return ApiLoader.getProvider(context)
+ .createMediaLibraryService2MediaLibrarySession(this, context, player, id,
+ (MediaLibrarySessionCallback) callback, volumeProvider, ratingType,
+ sessionActivity);
+ }
+
+ /**
+ * Notify subscribed controller about change in a parent's children.
+ *
+ * @param controller controller to notify
+ * @param parentId
+ * @param options
+ */
+ public void notifyChildrenChanged(@NonNull ControllerInfo controller,
+ @NonNull String parentId, @NonNull Bundle options) {
+ mProvider.notifyChildrenChanged_impl(controller, parentId, options);
+ }
+
+ /**
+ * Notify subscribed controller about change in a parent's children.
+ *
+ * @param parentId parent id
+ * @param options optional bundle
+ */
+ // This is for the backward compatibility.
+ public void notifyChildrenChanged(@NonNull String parentId, @Nullable Bundle options) {
+ mProvider.notifyChildrenChanged_impl(parentId, options);
}
- // TODO(jaewan): Place public methods here.
}
- public static abstract class MediaLibrarySessionCallback extends MediaSession2.SessionCallback {
+ public static class MediaLibrarySessionCallback extends MediaSession2.SessionCallback {
/**
* Called to get the root information for browsing by a particular client.
* <p>
@@ -85,8 +126,76 @@ public abstract class MediaLibraryService2 extends MediaSessionService2 {
* @see BrowserRoot#EXTRA_OFFLINE
* @see BrowserRoot#EXTRA_SUGGESTED
*/
- public abstract @Nullable BrowserRoot onGetRoot(
- @NonNull ControllerInfo controllerInfo, @Nullable Bundle rootHints);
+ public @Nullable BrowserRoot onGetRoot(@NonNull ControllerInfo controllerInfo,
+ @Nullable Bundle rootHints) {
+ return null;
+ }
+
+ /**
+ * Called to get the search result. Return search result here for the browser.
+ * <p>
+ * Return an empty list for no search result, and return {@code null} for the error.
+ *
+ * @param query The search query sent from the media browser. It contains keywords separated
+ * by space.
+ * @param extras The bundle of service-specific arguments sent from the media browser.
+ * @return search result. {@code null} for error.
+ */
+ public @Nullable List<MediaItem2> onSearch(@NonNull ControllerInfo controllerInfo,
+ @NonNull String query, @Nullable Bundle extras) {
+ return null;
+ }
+
+ /**
+ * Called to get the search result . Return result here for the browser.
+ * <p>
+ * Return an empty list for no search result, and return {@code null} for the error.
+ *
+ * @param itemId item id to get media item.
+ * @return media item2. {@code null} for error.
+ */
+ public @Nullable MediaItem2 onLoadItem(@NonNull ControllerInfo controllerInfo,
+ @NonNull String itemId) {
+ return null;
+ }
+
+ /**
+ * Called to get the search result. Return search result here for the browser.
+ * <p>
+ * Return an empty list for no search result, and return {@code null} for the error.
+ *
+ * @param parentId parent id to get children
+ * @param page number of page
+ * @param pageSize size of the page
+ * @param options
+ * @return list of children. Can be {@code null}.
+ */
+ public @Nullable List<MediaItem2> onLoadChildren(@NonNull ControllerInfo controller,
+ @NonNull String parentId, int page, int pageSize, @Nullable Bundle options) {
+ return null;
+ }
+
+ /**
+ * Called when a controller subscribes to the parent.
+ *
+ * @param controller controller
+ * @param parentId parent id
+ * @param options optional bundle
+ */
+ public void onSubscribed(@NonNull ControllerInfo controller,
+ String parentId, @Nullable Bundle options) {
+ }
+
+ /**
+ * Called when a controller unsubscribes to the parent.
+ *
+ * @param controller controller
+ * @param parentId parent id
+ * @param options optional bundle
+ */
+ public void onUnsubscribed(@NonNull ControllerInfo controller,
+ String parentId, @Nullable Bundle options) {
+ }
}
/**
@@ -113,7 +222,8 @@ public abstract class MediaLibraryService2 extends MediaSessionService2 {
@Override
public MediaLibrarySession build() throws IllegalStateException {
- return new MediaLibrarySession(mContext, mPlayer, mId, mCallback);
+ return new MediaLibrarySession(mContext, mPlayer, mId, mCallback,
+ mVolumeProvider, mRatingType, mSessionActivity);
}
}
@@ -141,4 +251,95 @@ public abstract class MediaLibraryService2 extends MediaSessionService2 {
*/
@Override
public @NonNull abstract MediaLibrarySession onCreateSession(String sessionId);
+
+ /**
+ * Contains information that the browser service needs to send to the client
+ * when first connected.
+ */
+ public static final class BrowserRoot {
+ /**
+ * The lookup key for a boolean that indicates whether the browser service should return a
+ * browser root for recently played media items.
+ *
+ * <p>When creating a media browser for a given media browser service, this key can be
+ * supplied as a root hint for retrieving media items that are recently played.
+ * If the media browser service can provide such media items, the implementation must return
+ * the key in the root hint when
+ * {@link MediaLibrarySessionCallback#onGetRoot(ControllerInfo, Bundle)} is called back.
+ *
+ * <p>The root hint may contain multiple keys.
+ *
+ * @see #EXTRA_OFFLINE
+ * @see #EXTRA_SUGGESTED
+ */
+ public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
+
+ /**
+ * The lookup key for a boolean that indicates whether the browser service should return a
+ * browser root for offline media items.
+ *
+ * <p>When creating a media browser for a given media browser service, this key can be
+ * supplied as a root hint for retrieving media items that are can be played without an
+ * internet connection.
+ * If the media browser service can provide such media items, the implementation must return
+ * the key in the root hint when
+ * {@link MediaLibrarySessionCallback#onGetRoot(ControllerInfo, Bundle)} is called back.
+ *
+ * <p>The root hint may contain multiple keys.
+ *
+ * @see #EXTRA_RECENT
+ * @see #EXTRA_SUGGESTED
+ */
+ public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
+
+ /**
+ * The lookup key for a boolean that indicates whether the browser service should return a
+ * browser root for suggested media items.
+ *
+ * <p>When creating a media browser for a given media browser service, this key can be
+ * supplied as a root hint for retrieving the media items suggested by the media browser
+ * service. The list of media items passed in {@link android.media.browse.MediaBrowser.SubscriptionCallback#onChildrenLoaded(String, List)}
+ * is considered ordered by relevance, first being the top suggestion.
+ * If the media browser service can provide such media items, the implementation must return
+ * the key in the root hint when
+ * {@link MediaLibrarySessionCallback#onGetRoot(ControllerInfo, Bundle)} is called back.
+ *
+ * <p>The root hint may contain multiple keys.
+ *
+ * @see #EXTRA_RECENT
+ * @see #EXTRA_OFFLINE
+ */
+ public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
+
+ final private String mRootId;
+ final private Bundle mExtras;
+
+ /**
+ * Constructs a browser root.
+ * @param rootId The root id for browsing.
+ * @param extras Any extras about the browser service.
+ */
+ public BrowserRoot(@NonNull String rootId, @Nullable Bundle extras) {
+ if (rootId == null) {
+ throw new IllegalArgumentException("The root id in BrowserRoot cannot be null. " +
+ "Use null for BrowserRoot instead.");
+ }
+ mRootId = rootId;
+ mExtras = extras;
+ }
+
+ /**
+ * Gets the root id for browsing.
+ */
+ public String getRootId() {
+ return mRootId;
+ }
+
+ /**
+ * Gets any extras about the browser service.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+ }
}
diff --git a/media/java/android/media/MediaMetadata2.java b/media/java/android/media/MediaMetadata2.java
new file mode 100644
index 000000000000..0e24db65fa54
--- /dev/null
+++ b/media/java/android/media/MediaMetadata2.java
@@ -0,0 +1,815 @@
+/*
+ * Copyright 2018 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;
+
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.ArrayMap;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+
+/**
+ * Contains metadata about an item, such as the title, artist, etc.
+ * @hide
+ */
+// TODO(jaewan): Move this to updatable
+public final class MediaMetadata2 {
+ private static final String TAG = "MediaMetadata2";
+
+ /**
+ * The title of the media.
+ */
+ public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+
+ /**
+ * The artist of the media.
+ */
+ public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+
+ /**
+ * The duration of the media in ms. A negative duration indicates that the
+ * duration is unknown (or infinite).
+ */
+ public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+
+ /**
+ * The album title for the media.
+ */
+ public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+
+ /**
+ * The author of the media.
+ */
+ public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+
+ /**
+ * The writer of the media.
+ */
+ public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+
+ /**
+ * The composer of the media.
+ */
+ public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+
+ /**
+ * The compilation status of the media.
+ */
+ public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+
+ /**
+ * The date the media was created or published. The format is unspecified
+ * but RFC 3339 is recommended.
+ */
+ public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
+
+ /**
+ * The year the media was created or published as a long.
+ */
+ public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+
+ /**
+ * The genre of the media.
+ */
+ public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+
+ /**
+ * The track number for the media.
+ */
+ public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+
+ /**
+ * The number of tracks in the media's original source.
+ */
+ public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+
+ /**
+ * The disc number for the media's original source.
+ */
+ public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+
+ /**
+ * The artist for the album of the media's original source.
+ */
+ public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+
+ /**
+ * The artwork for the media as a {@link Bitmap}.
+ *
+ * The artwork should be relatively small and may be scaled down
+ * if it is too large. For higher resolution artwork
+ * {@link #METADATA_KEY_ART_URI} should be used instead.
+ */
+ public static final String METADATA_KEY_ART = "android.media.metadata.ART";
+
+ /**
+ * The artwork for the media as a Uri style String.
+ */
+ public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
+
+ /**
+ * The artwork for the album of the media's original source as a
+ * {@link Bitmap}.
+ * The artwork should be relatively small and may be scaled down
+ * if it is too large. For higher resolution artwork
+ * {@link #METADATA_KEY_ALBUM_ART_URI} should be used instead.
+ */
+ public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+
+ /**
+ * The artwork for the album of the media's original source as a Uri style
+ * String.
+ */
+ public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
+
+ /**
+ * The user's rating for the media.
+ *
+ * @see Rating
+ */
+ public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+
+ /**
+ * The overall rating for the media.
+ *
+ * @see Rating
+ */
+ public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
+
+ /**
+ * A title that is suitable for display to the user. This will generally be
+ * the same as {@link #METADATA_KEY_TITLE} but may differ for some formats.
+ * When displaying media described by this metadata this should be preferred
+ * if present.
+ */
+ public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
+
+ /**
+ * A subtitle that is suitable for display to the user. When displaying a
+ * second line for media described by this metadata this should be preferred
+ * to other fields if present.
+ */
+ public static final String METADATA_KEY_DISPLAY_SUBTITLE
+ = "android.media.metadata.DISPLAY_SUBTITLE";
+
+ /**
+ * A description that is suitable for display to the user. When displaying
+ * more information for media described by this metadata this should be
+ * preferred to other fields if present.
+ */
+ public static final String METADATA_KEY_DISPLAY_DESCRIPTION
+ = "android.media.metadata.DISPLAY_DESCRIPTION";
+
+ /**
+ * An icon or thumbnail that is suitable for display to the user. When
+ * displaying an icon for media described by this metadata this should be
+ * preferred to other fields if present. This must be a {@link Bitmap}.
+ *
+ * The icon should be relatively small and may be scaled down
+ * if it is too large. For higher resolution artwork
+ * {@link #METADATA_KEY_DISPLAY_ICON_URI} should be used instead.
+ */
+ public static final String METADATA_KEY_DISPLAY_ICON
+ = "android.media.metadata.DISPLAY_ICON";
+
+ /**
+ * An icon or thumbnail that is suitable for display to the user. When
+ * displaying more information for media described by this metadata the
+ * display description should be preferred to other fields when present.
+ * This must be a Uri style String.
+ */
+ public static final String METADATA_KEY_DISPLAY_ICON_URI
+ = "android.media.metadata.DISPLAY_ICON_URI";
+
+ /**
+ * A String key for identifying the content. This value is specific to the
+ * service providing the content. If used, this should be a persistent
+ * unique key for the underlying content. It may be used with
+ * {@link MediaController2#playFromMediaId(String, Bundle)}
+ * to initiate playback when provided by a {@link MediaBrowser2} connected to
+ * the same app.
+ */
+ public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
+
+ /**
+ * A Uri formatted String representing the content. This value is specific to the
+ * service providing the content. It may be used with
+ * {@link MediaController2#playFromUri(Uri, Bundle)}
+ * to initiate playback when provided by a {@link MediaBrowser2} connected to
+ * the same app.
+ */
+ public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
+
+ /**
+ * The bluetooth folder type of the media specified in the section 6.10.2.2 of the Bluetooth
+ * AVRCP 1.5. It should be one of the following:
+ * <ul>
+ * <li>{@link #BT_FOLDER_TYPE_MIXED}</li>
+ * <li>{@link #BT_FOLDER_TYPE_TITLES}</li>
+ * <li>{@link #BT_FOLDER_TYPE_ALBUMS}</li>
+ * <li>{@link #BT_FOLDER_TYPE_ARTISTS}</li>
+ * <li>{@link #BT_FOLDER_TYPE_GENRES}</li>
+ * <li>{@link #BT_FOLDER_TYPE_PLAYLISTS}</li>
+ * <li>{@link #BT_FOLDER_TYPE_YEARS}</li>
+ * </ul>
+ */
+ public static final String METADATA_KEY_BT_FOLDER_TYPE
+ = "android.media.metadata.BT_FOLDER_TYPE";
+
+ /**
+ * The type of folder that is unknown or contains media elements of mixed types as specified in
+ * the section 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_MIXED = 0;
+
+ /**
+ * The type of folder that contains media elements only as specified in the section 6.10.2.2 of
+ * the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_TITLES = 1;
+
+ /**
+ * The type of folder that contains folders categorized by album as specified in the section
+ * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_ALBUMS = 2;
+
+ /**
+ * The type of folder that contains folders categorized by artist as specified in the section
+ * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_ARTISTS = 3;
+
+ /**
+ * The type of folder that contains folders categorized by genre as specified in the section
+ * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_GENRES = 4;
+
+ /**
+ * The type of folder that contains folders categorized by playlist as specified in the section
+ * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_PLAYLISTS = 5;
+
+ /**
+ * The type of folder that contains folders categorized by year as specified in the section
+ * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_YEARS = 6;
+
+ /**
+ * Whether the media is an advertisement. A value of 0 indicates it is not an advertisement. A
+ * value of 1 or non-zero indicates it is an advertisement. If not specified, this value is set
+ * to 0 by default.
+ */
+ public static final String METADATA_KEY_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
+
+ /**
+ * The download status of the media which will be used for later offline playback. It should be
+ * one of the following:
+ *
+ * <ul>
+ * <li>{@link #STATUS_NOT_DOWNLOADED}</li>
+ * <li>{@link #STATUS_DOWNLOADING}</li>
+ * <li>{@link #STATUS_DOWNLOADED}</li>
+ * </ul>
+ */
+ public static final String METADATA_KEY_DOWNLOAD_STATUS =
+ "android.media.metadata.DOWNLOAD_STATUS";
+
+ /**
+ * The status value to indicate the media item is not downloaded.
+ *
+ * @see #METADATA_KEY_DOWNLOAD_STATUS
+ */
+ public static final long STATUS_NOT_DOWNLOADED = 0;
+
+ /**
+ * The status value to indicate the media item is being downloaded.
+ *
+ * @see #METADATA_KEY_DOWNLOAD_STATUS
+ */
+ public static final long STATUS_DOWNLOADING = 1;
+
+ /**
+ * The status value to indicate the media item is downloaded for later offline playback.
+ *
+ * @see #METADATA_KEY_DOWNLOAD_STATUS
+ */
+ public static final long STATUS_DOWNLOADED = 2;
+
+ /**
+ * A {@link Bundle} extra.
+ * @hide
+ */
+ public static final String METADATA_KEY_EXTRA = "android.media.metadata.EXTRA";
+
+ /**
+ * @hide
+ */
+ @StringDef({METADATA_KEY_TITLE, METADATA_KEY_ARTIST, METADATA_KEY_ALBUM, METADATA_KEY_AUTHOR,
+ METADATA_KEY_WRITER, METADATA_KEY_COMPOSER, METADATA_KEY_COMPILATION,
+ METADATA_KEY_DATE, METADATA_KEY_GENRE, METADATA_KEY_ALBUM_ARTIST, METADATA_KEY_ART_URI,
+ METADATA_KEY_ALBUM_ART_URI, METADATA_KEY_DISPLAY_TITLE, METADATA_KEY_DISPLAY_SUBTITLE,
+ METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_KEY_DISPLAY_ICON_URI,
+ METADATA_KEY_MEDIA_ID, METADATA_KEY_MEDIA_URI})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TextKey {}
+
+ /**
+ * @hide
+ */
+ @StringDef({METADATA_KEY_DURATION, METADATA_KEY_YEAR, METADATA_KEY_TRACK_NUMBER,
+ METADATA_KEY_NUM_TRACKS, METADATA_KEY_DISC_NUMBER, METADATA_KEY_BT_FOLDER_TYPE,
+ METADATA_KEY_ADVERTISEMENT, METADATA_KEY_DOWNLOAD_STATUS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface LongKey {}
+
+ /**
+ * @hide
+ */
+ @StringDef({METADATA_KEY_ART, METADATA_KEY_ALBUM_ART, METADATA_KEY_DISPLAY_ICON})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BitmapKey {}
+
+ /**
+ * @hide
+ */
+ @StringDef({METADATA_KEY_USER_RATING, METADATA_KEY_RATING})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RatingKey {}
+
+ static final int METADATA_TYPE_LONG = 0;
+ static final int METADATA_TYPE_TEXT = 1;
+ static final int METADATA_TYPE_BITMAP = 2;
+ static final int METADATA_TYPE_RATING = 3;
+ static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
+
+ static {
+ METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
+ METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_ID, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_BT_FOLDER_TYPE, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_URI, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ADVERTISEMENT, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DOWNLOAD_STATUS, METADATA_TYPE_LONG);
+ }
+
+ private static final @TextKey String[] PREFERRED_DESCRIPTION_ORDER = {
+ METADATA_KEY_TITLE,
+ METADATA_KEY_ARTIST,
+ METADATA_KEY_ALBUM,
+ METADATA_KEY_ALBUM_ARTIST,
+ METADATA_KEY_WRITER,
+ METADATA_KEY_AUTHOR,
+ METADATA_KEY_COMPOSER
+ };
+
+ private static final @BitmapKey String[] PREFERRED_BITMAP_ORDER = {
+ METADATA_KEY_DISPLAY_ICON,
+ METADATA_KEY_ART,
+ METADATA_KEY_ALBUM_ART
+ };
+
+ private static final @TextKey String[] PREFERRED_URI_ORDER = {
+ METADATA_KEY_DISPLAY_ICON_URI,
+ METADATA_KEY_ART_URI,
+ METADATA_KEY_ALBUM_ART_URI
+ };
+
+ final Bundle mBundle;
+
+ /**
+ * @hide
+ */
+ public MediaMetadata2(Bundle bundle) {
+ mBundle = new Bundle(bundle);
+ }
+
+ /**
+ * Returns true if the given key is contained in the metadata
+ *
+ * @param key a String key
+ * @return true if the key exists in this metadata, false otherwise
+ */
+ public boolean containsKey(String key) {
+ return mBundle.containsKey(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if no mapping of
+ * the desired type exists for the given key or a null value is explicitly
+ * associated with the key.
+ *
+ * @param key The key the value is stored under
+ * @return a CharSequence value, or null
+ */
+ public CharSequence getText(@TextKey String key) {
+ return mBundle.getCharSequence(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if no mapping of
+ * the desired type exists for the given key or a null value is explicitly
+ * associated with the key.
+ *
+ * @
+ * @return media id. Can be {@code null}
+ */
+ public @Nullable String getMediaId() {
+ return getString(METADATA_KEY_MEDIA_ID);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if no mapping of
+ * the desired type exists for the given key or a null value is explicitly
+ * associated with the key.
+ *
+ * @param key The key the value is stored under
+ * @return a String value, or null
+ */
+ public String getString(@TextKey String key) {
+ CharSequence text = mBundle.getCharSequence(key);
+ if (text != null) {
+ return text.toString();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the value associated with the given key, or 0L if no long exists
+ * for the given key.
+ *
+ * @param key The key the value is stored under
+ * @return a long value
+ */
+ public long getLong(@LongKey String key) {
+ return mBundle.getLong(key, 0);
+ }
+
+ /**
+ * Return a {@link Rating2} for the given key or null if no rating exists for
+ * the given key.
+ *
+ * @param key The key the value is stored under
+ * @return A {@link Rating2} or null
+ */
+ public Rating2 getRating(@RatingKey String key) {
+ // TODO(jaewan): Add backward compatibility
+ Rating2 rating = null;
+ try {
+ rating = Rating2.fromBundle(mBundle.getBundle(key));
+ } catch (Exception e) {
+ // ignore, value was not a rating
+ Log.w(TAG, "Failed to retrieve a key as Rating.", e);
+ }
+ return rating;
+ }
+
+ /**
+ * Return a {@link Bitmap} for the given key or null if no bitmap exists for
+ * the given key.
+ *
+ * @param key The key the value is stored under
+ * @return A {@link Bitmap} or null
+ */
+ public Bitmap getBitmap(@BitmapKey String key) {
+ Bitmap bmp = null;
+ try {
+ bmp = mBundle.getParcelable(key);
+ } catch (Exception e) {
+ // ignore, value was not a bitmap
+ Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
+ }
+ return bmp;
+ }
+
+ /**
+ * Get the extra {@link Bundle} from the metadata object.
+ *
+ * @return A {@link Bundle} or {@code null}
+ */
+ public Bundle getExtra() {
+ try {
+ return mBundle.getBundle(METADATA_KEY_EXTRA);
+ } catch (Exception e) {
+ // ignore, value was not an bundle
+ Log.w(TAG, "Failed to retrieve an extra");
+ }
+ return null;
+ }
+
+ /**
+ * Get the number of fields in this metadata.
+ *
+ * @return The number of fields in the metadata.
+ */
+ public int size() {
+ return mBundle.size();
+ }
+
+ /**
+ * Returns a Set containing the Strings used as keys in this metadata.
+ *
+ * @return a Set of String keys
+ */
+ public Set<String> keySet() {
+ return mBundle.keySet();
+ }
+
+ /**
+ * Gets the bundle backing the metadata object. This is available to support
+ * backwards compatibility. Apps should not modify the bundle directly.
+ *
+ * @return The Bundle backing this metadata.
+ */
+ public Bundle getBundle() {
+ return mBundle;
+ }
+
+ /**
+ * Use to build MediaMetadata2 objects. The system defined metadata keys must
+ * use the appropriate data type.
+ */
+ public static final class Builder {
+ private final Bundle mBundle;
+
+ /**
+ * Create an empty Builder. Any field that should be included in the
+ * {@link MediaMetadata2} must be added.
+ */
+ public Builder() {
+ mBundle = new Bundle();
+ }
+
+ /**
+ * Create a Builder using a {@link MediaMetadata2} instance to set the
+ * initial values. All fields in the source metadata will be included in
+ * the new metadata. Fields can be overwritten by adding the same key.
+ *
+ * @param source
+ */
+ public Builder(MediaMetadata2 source) {
+ mBundle = new Bundle(source.mBundle);
+ }
+
+ /**
+ * Create a Builder using a {@link MediaMetadata2} instance to set
+ * initial values, but replace bitmaps with a scaled down copy if they
+ * are larger than maxBitmapSize.
+ *
+ * @param source The original metadata to copy.
+ * @param maxBitmapSize The maximum height/width for bitmaps contained
+ * in the metadata.
+ * @hide
+ */
+ public Builder(MediaMetadata2 source, int maxBitmapSize) {
+ this(source);
+ for (String key : mBundle.keySet()) {
+ Object value = mBundle.get(key);
+ if (value instanceof Bitmap) {
+ Bitmap bmp = (Bitmap) value;
+ if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) {
+ putBitmap(key, scaleBitmap(bmp, maxBitmapSize));
+ }
+ }
+ }
+ }
+
+ /**
+ * Put a CharSequence value into the metadata. Custom keys may be used,
+ * but if the METADATA_KEYs defined in this class are used they may only
+ * be one of the following:
+ * <ul>
+ * <li>{@link #METADATA_KEY_TITLE}</li>
+ * <li>{@link #METADATA_KEY_ARTIST}</li>
+ * <li>{@link #METADATA_KEY_ALBUM}</li>
+ * <li>{@link #METADATA_KEY_AUTHOR}</li>
+ * <li>{@link #METADATA_KEY_WRITER}</li>
+ * <li>{@link #METADATA_KEY_COMPOSER}</li>
+ * <li>{@link #METADATA_KEY_DATE}</li>
+ * <li>{@link #METADATA_KEY_GENRE}</li>
+ * <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
+ * <li>{@link #METADATA_KEY_ART_URI}</li>
+ * <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_TITLE}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}</li>
+ * </ul>
+ *
+ * @param key The key for referencing this value
+ * @param value The CharSequence value to store
+ * @return The Builder to allow chaining
+ */
+ public Builder putText(@TextKey String key, CharSequence value) {
+ if (METADATA_KEYS_TYPE.containsKey(key)) {
+ if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a CharSequence");
+ }
+ }
+ mBundle.putCharSequence(key, value);
+ return this;
+ }
+
+ /**
+ * Put a String value into the metadata. Custom keys may be used, but if
+ * the METADATA_KEYs defined in this class are used they may only be one
+ * of the following:
+ * <ul>
+ * <li>{@link #METADATA_KEY_TITLE}</li>
+ * <li>{@link #METADATA_KEY_ARTIST}</li>
+ * <li>{@link #METADATA_KEY_ALBUM}</li>
+ * <li>{@link #METADATA_KEY_AUTHOR}</li>
+ * <li>{@link #METADATA_KEY_WRITER}</li>
+ * <li>{@link #METADATA_KEY_COMPOSER}</li>
+ * <li>{@link #METADATA_KEY_DATE}</li>
+ * <li>{@link #METADATA_KEY_GENRE}</li>
+ * <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
+ * <li>{@link #METADATA_KEY_ART_URI}</li>
+ * <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_TITLE}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}</li>
+ * </ul>
+ *
+ * @param key The key for referencing this value
+ * @param value The String value to store
+ * @return The Builder to allow chaining
+ */
+ public Builder putString(@TextKey String key, String value) {
+ if (METADATA_KEYS_TYPE.containsKey(key)) {
+ if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a String");
+ }
+ }
+ mBundle.putCharSequence(key, value);
+ return this;
+ }
+
+ /**
+ * Put a long value into the metadata. Custom keys may be used, but if
+ * the METADATA_KEYs defined in this class are used they may only be one
+ * of the following:
+ * <ul>
+ * <li>{@link #METADATA_KEY_DURATION}</li>
+ * <li>{@link #METADATA_KEY_TRACK_NUMBER}</li>
+ * <li>{@link #METADATA_KEY_NUM_TRACKS}</li>
+ * <li>{@link #METADATA_KEY_DISC_NUMBER}</li>
+ * <li>{@link #METADATA_KEY_YEAR}</li>
+ * <li>{@link #METADATA_KEY_BT_FOLDER_TYPE}</li>
+ * <li>{@link #METADATA_KEY_ADVERTISEMENT}</li>
+ * <li>{@link #METADATA_KEY_DOWNLOAD_STATUS}</li>
+ * </ul>
+ *
+ * @param key The key for referencing this value
+ * @param value The String value to store
+ * @return The Builder to allow chaining
+ */
+ public Builder putLong(@LongKey String key, long value) {
+ if (METADATA_KEYS_TYPE.containsKey(key)) {
+ if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_LONG) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a long");
+ }
+ }
+ mBundle.putLong(key, value);
+ return this;
+ }
+
+ /**
+ * Put a {@link Rating2} into the metadata. Custom keys may be used, but
+ * if the METADATA_KEYs defined in this class are used they may only be
+ * one of the following:
+ * <ul>
+ * <li>{@link #METADATA_KEY_RATING}</li>
+ * <li>{@link #METADATA_KEY_USER_RATING}</li>
+ * </ul>
+ *
+ * @param key The key for referencing this value
+ * @param value The String value to store
+ * @return The Builder to allow chaining
+ */
+ public Builder putRating(@RatingKey String key, Rating2 value) {
+ if (METADATA_KEYS_TYPE.containsKey(key)) {
+ if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_RATING) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a Rating");
+ }
+ }
+ mBundle.putBundle(key, value.toBundle());
+
+ return this;
+ }
+
+ /**
+ * Put a {@link Bitmap} into the metadata. Custom keys may be used, but
+ * if the METADATA_KEYs defined in this class are used they may only be
+ * one of the following:
+ * <ul>
+ * <li>{@link #METADATA_KEY_ART}</li>
+ * <li>{@link #METADATA_KEY_ALBUM_ART}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_ICON}</li>
+ * </ul>
+ * Large bitmaps may be scaled down by the system when
+ * {@link android.media.session.MediaSession#setMetadata} is called.
+ * To pass full resolution images {@link Uri Uris} should be used with
+ * {@link #putString}.
+ *
+ * @param key The key for referencing this value
+ * @param value The Bitmap to store
+ * @return The Builder to allow chaining
+ */
+ public Builder putBitmap(@BitmapKey String key, Bitmap value) {
+ if (METADATA_KEYS_TYPE.containsKey(key)) {
+ if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a Bitmap");
+ }
+ }
+ mBundle.putParcelable(key, value);
+ return this;
+ }
+
+ /**
+ * Set an extra {@link Bundle} into the metadata.
+ */
+ public Builder setExtra(Bundle bundle) {
+ mBundle.putBundle(METADATA_KEY_EXTRA, bundle);
+ return this;
+ }
+
+ /**
+ * Creates a {@link MediaMetadata2} instance with the specified fields.
+ *
+ * @return The new MediaMetadata2 instance
+ */
+ public MediaMetadata2 build() {
+ return new MediaMetadata2(mBundle);
+ }
+
+ private Bitmap scaleBitmap(Bitmap bmp, int maxSize) {
+ float maxSizeF = maxSize;
+ float widthScale = maxSizeF / bmp.getWidth();
+ float heightScale = maxSizeF / bmp.getHeight();
+ float scale = Math.min(widthScale, heightScale);
+ int height = (int) (bmp.getHeight() * scale);
+ int width = (int) (bmp.getWidth() * scale);
+ return Bitmap.createScaledBitmap(bmp, width, height, true);
+ }
+ }
+}
+
diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java
index 0932fea43d2a..6fdf7a8b906c 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/java/android/media/MediaSession2.java
@@ -18,7 +18,12 @@ package android.media;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
import android.media.MediaPlayerBase.PlaybackListener;
import android.media.session.MediaSession;
import android.media.session.MediaSession.Callback;
@@ -26,9 +31,11 @@ import android.media.session.PlaybackState;
import android.media.update.ApiLoader;
import android.media.update.MediaSession2Provider;
import android.media.update.MediaSession2Provider.ControllerInfoProvider;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
+import android.os.ResultReceiver;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -69,8 +76,6 @@ import java.util.List;
*/
// TODO(jaewan): Unhide
// TODO(jaewan): Revisit comments. Currently it's borrowed from the MediaSession.
-// TODO(jaewan): Add explicit release(), and make token @NonNull. Session will be active while the
-// session exists, and controllers will be invalidated when session becomes inactive.
// TODO(jaewan): Should we support thread safe? It may cause tricky issue such as b/63797089
// TODO(jaewan): Should we make APIs for MediaSessionService2 public? It's helpful for
// developers that doesn't want to override from Browser, but user may not use this
@@ -79,12 +84,30 @@ public class MediaSession2 implements AutoCloseable {
private final MediaSession2Provider mProvider;
// Note: Do not define IntDef because subclass can add more command code on top of these.
+ // TODO(jaewan): Shouldn't we pull out?
public static final int COMMAND_CODE_CUSTOM = 0;
public static final int COMMAND_CODE_PLAYBACK_START = 1;
public static final int COMMAND_CODE_PLAYBACK_PAUSE = 2;
public static final int COMMAND_CODE_PLAYBACK_STOP = 3;
public static final int COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM = 4;
public static final int COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM = 5;
+ public static final int COMMAND_CODE_PLAYBACK_PREPARE = 6;
+ public static final int COMMAND_CODE_PLAYBACK_FAST_FORWARD = 7;
+ public static final int COMMAND_CODE_PLAYBACK_REWIND = 8;
+ public static final int COMMAND_CODE_PLAYBACK_SEEK_TO = 9;
+ public static final int COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM = 10;
+
+ public static final int COMMAND_CODE_PLAYLIST_GET = 11;
+ public static final int COMMAND_CODE_PLAYLIST_ADD = 12;
+ public static final int COMMAND_CODE_PLAYLIST_REMOVE = 13;
+
+ public static final int COMMAND_CODE_PLAY_FROM_MEDIA_ID = 14;
+ public static final int COMMAND_CODE_PLAY_FROM_URI = 15;
+ public static final int COMMAND_CODE_PLAY_FROM_SEARCH = 16;
+
+ public static final int COMMAND_CODE_PREPARE_FROM_MEDIA_ID = 17;
+ public static final int COMMAND_CODE_PREPARE_FROM_URI = 18;
+ public static final int COMMAND_CODE_PREPARE_FROM_SEARCH = 19;
/**
* Define a command that a {@link MediaController2} can send to a {@link MediaSession2}.
@@ -299,19 +322,133 @@ public class MediaSession2 implements AutoCloseable {
}
/**
- * Called when a controller sent a command to the session. You can also reject the request
- * by return {@code false}.
- * <p>
- * This method will be called on the session handler.
+ * Called when a controller is disconnected
+ *
+ * @param controller controller information
+ */
+ public void onDisconnected(@NonNull ControllerInfo controller) { }
+
+ /**
+ * Called when a controller sent a command to the session, and the command will be sent to
+ * the player directly unless you reject the request by {@code false}.
*
* @param controller controller information.
* @param command a command. This method will be called for every single command.
* @return {@code true} if you want to accept incoming command. {@code false} otherwise.
*/
+ // TODO(jaewan): Add more documentations (or make it clear) which commands can be filtered
+ // with this.
public boolean onCommandRequest(@NonNull ControllerInfo controller,
@NonNull Command command) {
return true;
}
+
+ /**
+ * Called when a controller set rating on the currently playing contents.
+ *
+ * @param
+ */
+ public void onSetRating(@NonNull ControllerInfo controller, @NonNull Rating2 rating) { }
+
+ /**
+ * Called when a controller sent a custom command.
+ *
+ * @param controller controller information
+ * @param customCommand custom command.
+ * @param args optional arguments
+ * @param cb optional result receiver
+ */
+ public void onCustomCommand(@NonNull ControllerInfo controller,
+ @NonNull Command customCommand, @Nullable Bundle args,
+ @Nullable ResultReceiver cb) { }
+
+ /**
+ * Override to handle requests to prepare for playing a specific mediaId.
+ * During the preparation, a session should not hold audio focus in order to allow other
+ * sessions play seamlessly. The state of playback should be updated to
+ * {@link PlaybackState#STATE_PAUSED} after the preparation is done.
+ * <p>
+ * The playback of the prepared content should start in the later calls of
+ * {@link MediaSession2#play()}.
+ * <p>
+ * Override {@link #onPlayFromMediaId} to handle requests for starting
+ * playback without preparation.
+ */
+ public void onPlayFromMediaId(@NonNull ControllerInfo controller,
+ @NonNull String mediaId, @Nullable Bundle extras) { }
+
+ /**
+ * Override to handle requests to prepare playback from a search query. An empty query
+ * indicates that the app may prepare any music. The implementation should attempt to make a
+ * smart choice about what to play. During the preparation, a session should not hold audio
+ * focus in order to allow other sessions play seamlessly. The state of playback should be
+ * updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done.
+ * <p>
+ * The playback of the prepared content should start in the later calls of
+ * {@link MediaSession2#play()}.
+ * <p>
+ * Override {@link #onPlayFromSearch} to handle requests for starting playback without
+ * preparation.
+ */
+ public void onPlayFromSearch(@NonNull ControllerInfo controller,
+ @NonNull String query, @Nullable Bundle extras) { }
+
+ /**
+ * Override to handle requests to prepare a specific media item represented by a URI.
+ * During the preparation, a session should not hold audio focus in order to allow
+ * other sessions play seamlessly. The state of playback should be updated to
+ * {@link PlaybackState#STATE_PAUSED} after the preparation is done.
+ * <p>
+ * The playback of the prepared content should start in the later calls of
+ * {@link MediaSession2#play()}.
+ * <p>
+ * Override {@link #onPlayFromUri} to handle requests for starting playback without
+ * preparation.
+ */
+ public void onPlayFromUri(@NonNull ControllerInfo controller,
+ @NonNull String uri, @Nullable Bundle extras) { }
+
+ /**
+ * Override to handle requests to play a specific mediaId.
+ */
+ public void onPrepareFromMediaId(@NonNull ControllerInfo controller,
+ @NonNull String mediaId, @Nullable Bundle extras) { }
+
+ /**
+ * Override to handle requests to begin playback from a search query. An
+ * empty query indicates that the app may play any music. The
+ * implementation should attempt to make a smart choice about what to
+ * play.
+ */
+ public void onPrepareFromSearch(@NonNull ControllerInfo controller,
+ @NonNull String query, @Nullable Bundle extras) { }
+
+ /**
+ * Override to handle requests to play a specific media item represented by a URI.
+ */
+ public void prepareFromUri(@NonNull ControllerInfo controller,
+ @NonNull Uri uri, @Nullable Bundle extras) { }
+
+ /**
+ * Called when a controller wants to add a {@link MediaItem2} at the specified position
+ * in the play queue.
+ * <p>
+ * The item from the media controller wouldn't have valid data source descriptor because
+ * it would have been anonymized when it's sent to the remote process.
+ *
+ * @param item The media item to be inserted.
+ * @param index The index at which the item is to be inserted.
+ */
+ public void onAddPlaylistItem(@NonNull ControllerInfo controller,
+ @NonNull MediaItem2 item, int index) { }
+
+ /**
+ * Called when a controller wants to remove the {@link MediaItem2}
+ *
+ * @param item
+ */
+ // Can we do this automatically?
+ public void onRemovePlaylistItem(@NonNull MediaItem2 item) { }
};
/**
@@ -325,6 +462,9 @@ public class MediaSession2 implements AutoCloseable {
final MediaPlayerBase mPlayer;
String mId;
C mCallback;
+ VolumeProvider mVolumeProvider;
+ int mRatingType;
+ PendingIntent mSessionActivity;
/**
* Constructor.
@@ -349,6 +489,50 @@ public class MediaSession2 implements AutoCloseable {
}
/**
+ * Set volume provider to configure this session to use remote volume handling.
+ * This must be called to receive volume button events, otherwise the system
+ * will adjust the appropriate stream volume for this session's player.
+ * <p>
+ * Set {@code null} to reset.
+ *
+ * @param volumeProvider The provider that will handle volume changes. Can be {@code null}
+ */
+ public T setVolumeProvider(@Nullable VolumeProvider volumeProvider) {
+ mVolumeProvider = volumeProvider;
+ return (T) this;
+ }
+
+ /**
+ * Set the style of rating used by this session. Apps trying to set the
+ * rating should use this style. Must be one of the following:
+ * <ul>
+ * <li>{@link Rating2#RATING_NONE}</li>
+ * <li>{@link Rating2#RATING_3_STARS}</li>
+ * <li>{@link Rating2#RATING_4_STARS}</li>
+ * <li>{@link Rating2#RATING_5_STARS}</li>
+ * <li>{@link Rating2#RATING_HEART}</li>
+ * <li>{@link Rating2#RATING_PERCENTAGE}</li>
+ * <li>{@link Rating2#RATING_THUMB_UP_DOWN}</li>
+ * </ul>
+ */
+ public T setRatingType(@Rating2.Style int type) {
+ mRatingType = type;
+ return (T) this;
+ }
+
+ /**
+ * Set an intent for launching UI for this Session. This can be used as a
+ * quick link to an ongoing media screen. The intent should be for an
+ * activity that may be started using {@link Activity#startActivity(Intent)}.
+ *
+ * @param pi The intent to launch to show UI for this session.
+ */
+ public T setSessionActivity(@Nullable PendingIntent pi) {
+ mSessionActivity = pi;
+ return (T) this;
+ }
+
+ /**
* Set ID of the session. If it's not set, an empty string with used to create a session.
* <p>
* Use this if and only if your app supports multiple playback at the same time and also
@@ -406,7 +590,8 @@ public class MediaSession2 implements AutoCloseable {
if (mCallback == null) {
mCallback = new SessionCallback();
}
- return new MediaSession2(mContext, mPlayer, mId, mCallback);
+ return new MediaSession2(mContext, mPlayer, mId, mCallback,
+ mVolumeProvider, mRatingType, mSessionActivity);
}
}
@@ -654,6 +839,39 @@ public class MediaSession2 implements AutoCloseable {
}
/**
+ * Parameter for the playlist.
+ */
+ // TODO(jaewan): add fromBundle()/toBundle()
+ public class PlaylistParam {
+ private MediaMetadata2 mPlaylistMetadata;
+
+ // It's mixture of shuffle mode and repeat mode. Needs to be separated for avrcp 1.6 support
+ // PlaybackState#REPEAT_MODE_ALL
+ // PlaybackState#REPEAT_MODE_GROUP <- for avrcp 1.6
+ // PlaybackState#REPEAT_MODE_INVALID
+ // PlaybackState#REPEAT_MODE_NONE
+ // PlaybackState#REPEAT_MODE_ONE
+ // PlaybackState#SHUFFLE_MODE_ALL
+ // PlaybackState#SHUFFLE_MODE_GROUP <- for avrcp 1.6
+ // PlaybackState#SHUFFLE_MODE_INVALID
+ // PlaybackState#SHUFFLE_MODE_NONE
+ private int mLoopingMode;
+
+ public PlaylistParam(int loopingMode, @Nullable MediaMetadata2 playlistMetadata) {
+ mLoopingMode = loopingMode;
+ mPlaylistMetadata = playlistMetadata;
+ }
+
+ public int getLoopingMode() {
+ return mLoopingMode;
+ }
+
+ public MediaMetadata2 getPlaylistMetadata() {
+ return mPlaylistMetadata;
+ }
+ }
+
+ /**
* Constructor is hidden and apps can only instantiate indirectly through {@link Builder}.
* <p>
* This intended behavior and here's the reasons.
@@ -668,10 +886,19 @@ public class MediaSession2 implements AutoCloseable {
* @hide
*/
MediaSession2(Context context, MediaPlayerBase player, String id,
- SessionCallback callback) {
+ SessionCallback callback, VolumeProvider volumeProvider, int ratingType,
+ PendingIntent sessionActivity) {
super();
- mProvider = ApiLoader.getProvider(context)
- .createMediaSession2(this, context, player, id, callback);
+ mProvider = createProvider(context, player, id, callback,
+ volumeProvider, ratingType, sessionActivity);
+ }
+
+ MediaSession2Provider createProvider(Context context, MediaPlayerBase player, String id,
+ SessionCallback callback, VolumeProvider volumeProvider, int ratingType,
+ PendingIntent sessionActivity) {
+ return ApiLoader.getProvider(context)
+ .createMediaSession2(this, context, player, id, callback,
+ volumeProvider, ratingType, sessionActivity);
}
/**
@@ -690,17 +917,30 @@ public class MediaSession2 implements AutoCloseable {
* If the new player is successfully set, {@link PlaybackListener}
* will be called to tell the current playback state of the new player.
* <p>
- * Calling this method with {@code null} will disconnect binding connection between the
- * controllers and also release this object.
+ * You can also specify a volume provider. If so, playback in the player is considered as
+ * remote playback.
*
* @param player a {@link MediaPlayerBase} that handles actual media playback in your app.
- * It shouldn't be {@link MediaSession2} nor {@link MediaController2}.
* @throws IllegalArgumentException if the player is {@code null}.
*/
- public void setPlayer(@NonNull MediaPlayerBase player) throws IllegalArgumentException {
+ public void setPlayer(@NonNull MediaPlayerBase player) {
mProvider.setPlayer_impl(player);
}
+ /**
+ * Set the underlying {@link MediaPlayerBase} with the volume provider for remote playback.
+ *
+ * @param player a {@link MediaPlayerBase} that handles actual media playback in your app.
+ * @param volumeProvider a volume provider
+ * @see #setPlayer(MediaPlayerBase)
+ * @see Builder#setVolumeProvider(VolumeProvider)
+ * @throws IllegalArgumentException if a parameter is {@code null}.
+ */
+ public void setPlayer(@NonNull MediaPlayerBase player, @NonNull VolumeProvider volumeProvider)
+ throws IllegalArgumentException {
+ mProvider.setPlayer_impl(player, volumeProvider);
+ }
+
@Override
public void close() {
mProvider.close_impl();
@@ -726,6 +966,34 @@ public class MediaSession2 implements AutoCloseable {
}
/**
+ * Sets the {@link AudioAttributes} to be used during the playback of the video.
+ *
+ * @param attributes non-null <code>AudioAttributes</code>.
+ */
+ public void setAudioAttributes(@NonNull AudioAttributes attributes) {
+ mProvider.setAudioAttributes_impl(attributes);
+ }
+
+ /**
+ * Sets which type of audio focus will be requested during the playback, or configures playback
+ * to not request audio focus. Valid values for focus requests are
+ * {@link AudioManager#AUDIOFOCUS_GAIN}, {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT},
+ * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, and
+ * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. Or use
+ * {@link AudioManager#AUDIOFOCUS_NONE} to express that audio focus should not be
+ * requested when playback starts. You can for instance use this when playing a silent animation
+ * through this class, and you don't want to affect other audio applications playing in the
+ * background.
+ *
+ * @param focusGain the type of audio focus gain that will be requested, or
+ * {@link AudioManager#AUDIOFOCUS_NONE} to disable the use audio focus during
+ * playback.
+ */
+ public void setAudioFocusRequest(int focusGain) {
+ mProvider.setAudioFocusRequest_impl(focusGain);
+ }
+
+ /**
* Sets ordered list of {@link CommandButton} for controllers to build UI with it.
* <p>
* It's up to controller's decision how to represent the layout in its own UI.
@@ -750,6 +1018,48 @@ public class MediaSession2 implements AutoCloseable {
}
/**
+ * Set the new allowed command group for the controller
+ *
+ * @param controller controller to change allowed commands
+ * @param commands new allowed commands
+ */
+ public void setAllowedCommands(@NonNull ControllerInfo controller,
+ @NonNull CommandGroup commands) {
+ mProvider.setAllowedCommands_impl(controller, commands);
+ }
+
+ /**
+ * Notify changes in metadata of previously set playlist. Controller will get the whole set of
+ * playlist again.
+ */
+ public void notifyMetadataChanged() {
+ mProvider.notifyMetadataChanged_impl();
+ }
+
+ /**
+ * Send custom command to all connected controllers.
+ *
+ * @param command a command
+ * @param args optional argument
+ */
+ public void sendCustomCommand(@NonNull Command command, @Nullable Bundle args) {
+ mProvider.sendCustomCommand_impl(command, args);
+ }
+
+ /**
+ * Send custom command to a specific controller.
+ *
+ * @param command a command
+ * @param args optional argument
+ * @param receiver result receiver for the session
+ */
+ public void sendCustomCommand(@NonNull ControllerInfo controller, @NonNull Command command,
+ @Nullable Bundle args, @Nullable ResultReceiver receiver) {
+ // Equivalent to the MediaController.sendCustomCommand(Action action, ResultReceiver r);
+ mProvider.sendCustomCommand_impl(controller, command, args, receiver);
+ }
+
+ /**
* Play playback
*/
public void play() {
@@ -784,33 +1094,66 @@ public class MediaSession2 implements AutoCloseable {
mProvider.skipToNext_impl();
}
- public @NonNull PlaybackState getPlaybackState() {
- return mProvider.getPlaybackState_impl();
+ /**
+ * Request that the player prepare its playback. In other words, other sessions can continue
+ * to play during the preparation of this session. This method can be used to speed up the
+ * start of the playback. Once the preparation is done, the session will change its playback
+ * state to {@link PlaybackState#STATE_PAUSED}. Afterwards, {@link #play} can be called to
+ * start playback.
+ */
+ public void prepare() {
+ mProvider.prepare_impl();
}
/**
- * Add a {@link PlaybackListener} to listen changes in the
- * underlying {@link MediaPlayerBase} which is previously set by
- * {@link #setPlayer(MediaPlayerBase)}.
- * <p>
- * Added listeners will be also called when the underlying player is changed.
+ * Start fast forwarding. If playback is already fast forwarding this may increase the rate.
+ */
+ public void fastForward() {
+ mProvider.fastForward_impl();
+ }
+
+ /**
+ * Start rewinding. If playback is already rewinding this may increase the rate.
+ */
+ public void rewind() {
+ mProvider.rewind_impl();
+ }
+
+ /**
+ * Move to a new location in the media stream.
*
- * @param listener the listener that will be run
- * @param handler the Handler that will receive the listener
- * @throws IllegalArgumentException when either the listener or handler is {@code null}.
+ * @param pos Position to move to, in milliseconds.
*/
- // TODO(jaewan): Can handler be null? Follow API guideline after it's finalized.
- public void addPlaybackListener(@NonNull PlaybackListener listener, @NonNull Handler handler) {
- mProvider.addPlaybackListener_impl(listener, handler);
+ public void seekTo(long pos) {
+ mProvider.seekTo_impl(pos);
}
/**
- * Remove previously added {@link PlaybackListener}.
+ * Sets the index of current DataSourceDesc in the play list to be played.
*
- * @param listener the listener to be removed
- * @throws IllegalArgumentException if the listener is {@code null}.
+ * @param index the index of DataSourceDesc in the play list you want to play
+ * @throws IllegalArgumentException if the play list is null
+ * @throws NullPointerException if index is outside play list range
*/
- public void removePlaybackListener(PlaybackListener listener) {
- mProvider.removePlaybackListener_impl(listener);
+ public void setCurrentPlaylistItem(int index) {
+ mProvider.setCurrentPlaylistItem_impl(index);
+ }
+
+ /**
+ * @hide
+ */
+ public void skipForward() {
+ // To match with KEYCODE_MEDIA_SKIP_FORWARD
+ }
+
+ /**
+ * @hide
+ */
+ public void skipBackward() {
+ // To match with KEYCODE_MEDIA_SKIP_BACKWARD
+ }
+
+ public void setPlaylist(@NonNull List<MediaItem2> playlist, @NonNull PlaylistParam param) {
+ mProvider.setPlaylist_impl(playlist, param);
}
}
diff --git a/media/java/android/media/PlaybackState2.java b/media/java/android/media/PlaybackState2.java
new file mode 100644
index 000000000000..3740aeab9562
--- /dev/null
+++ b/media/java/android/media/PlaybackState2.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2018 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;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Playback state for a {@link MediaPlayerBase}, to be shared between {@link MediaSession2} and
+ * {@link MediaController2}. This includes a playback state {@link #STATE_PLAYING},
+ * the current playback position and extra.
+ * @hide
+ */
+// TODO(jaewan): Move to updatable
+// TODO(jaewan): Add fromBundle() toBundle()
+public final class PlaybackState2 {
+ private static final String TAG = "PlaybackState2";
+
+ // TODO(jaewan): Replace states from MediaPlayer2
+ /**
+ * @hide
+ */
+ @IntDef({STATE_NONE, STATE_STOPPED, STATE_PREPARED, STATE_PAUSED, STATE_PLAYING,
+ STATE_FINISH, STATE_BUFFERING, STATE_ERROR})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface State {}
+
+ /**
+ * This is the default playback state and indicates that no media has been
+ * added yet, or the performer has been reset and has no content to play.
+ */
+ public final static int STATE_NONE = 0;
+
+ /**
+ * State indicating this item is currently stopped.
+ */
+ public final static int STATE_STOPPED = 1;
+
+ /**
+ * State indicating this item is currently prepared
+ */
+ public final static int STATE_PREPARED = 2;
+
+ /**
+ * State indicating this item is currently paused.
+ */
+ public final static int STATE_PAUSED = 3;
+
+ /**
+ * State indicating this item is currently playing.
+ */
+ public final static int STATE_PLAYING = 4;
+
+ /**
+ * State indicating the playback reaches the end of the item.
+ */
+ public final static int STATE_FINISH = 5;
+
+ /**
+ * State indicating this item is currently buffering and will begin playing
+ * when enough data has buffered.
+ */
+ public final static int STATE_BUFFERING = 6;
+
+ /**
+ * State indicating this item is currently in an error state. The error
+ * message should also be set when entering this state.
+ */
+ public final static int STATE_ERROR = 7;
+
+ /**
+ * Use this value for the position to indicate the position is not known.
+ */
+ public final static long PLAYBACK_POSITION_UNKNOWN = -1;
+
+ private final int mState;
+ private final long mPosition;
+ private final long mBufferedPosition;
+ private final float mSpeed;
+ private final CharSequence mErrorMessage;
+ private final long mUpdateTime;
+ private final long mActiveItemId;
+
+ private PlaybackState2(int state, long position, long updateTime, float speed,
+ long bufferedPosition, long activeItemId, CharSequence error) {
+ mState = state;
+ mPosition = position;
+ mSpeed = speed;
+ mUpdateTime = updateTime;
+ mBufferedPosition = bufferedPosition;
+ mActiveItemId = activeItemId;
+ mErrorMessage = error;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder bob = new StringBuilder("PlaybackState {");
+ bob.append("state=").append(mState);
+ bob.append(", position=").append(mPosition);
+ bob.append(", buffered position=").append(mBufferedPosition);
+ bob.append(", speed=").append(mSpeed);
+ bob.append(", updated=").append(mUpdateTime);
+ bob.append(", active item id=").append(mActiveItemId);
+ bob.append(", error=").append(mErrorMessage);
+ bob.append("}");
+ return bob.toString();
+ }
+
+ /**
+ * Get the current state of playback. One of the following:
+ * <ul>
+ * <li> {@link PlaybackState2#STATE_NONE}</li>
+ * <li> {@link PlaybackState2#STATE_STOPPED}</li>
+ * <li> {@link PlaybackState2#STATE_PLAYING}</li>
+ * <li> {@link PlaybackState2#STATE_PAUSED}</li>
+ * <li> {@link PlaybackState2#STATE_FAST_FORWARDING}</li>
+ * <li> {@link PlaybackState2#STATE_REWINDING}</li>
+ * <li> {@link PlaybackState2#STATE_BUFFERING}</li>
+ * <li> {@link PlaybackState2#STATE_ERROR}</li>
+ * <li> {@link PlaybackState2#STATE_CONNECTING}</li>
+ * <li> {@link PlaybackState2#STATE_SKIPPING_TO_PREVIOUS}</li>
+ * <li> {@link PlaybackState2#STATE_SKIPPING_TO_NEXT}</li>
+ * <li> {@link PlaybackState2#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
+ * </ul>
+ */
+ @State
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Get the current playback position in ms.
+ */
+ public long getPosition() {
+ return mPosition;
+ }
+
+ /**
+ * Get the current buffered position in ms. This is the farthest playback
+ * point that can be reached from the current position using only buffered
+ * content.
+ */
+ public long getBufferedPosition() {
+ return mBufferedPosition;
+ }
+
+ /**
+ * Get the current playback speed as a multiple of normal playback. This
+ * should be negative when rewinding. A value of 1 means normal playback and
+ * 0 means paused.
+ *
+ * @return The current speed of playback.
+ */
+ public float getPlaybackSpeed() {
+ return mSpeed;
+ }
+
+ /**
+ * Get a user readable error message. This should be set when the state is
+ * {@link PlaybackState2#STATE_ERROR}.
+ */
+ public CharSequence getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ /**
+ * Get the elapsed real time at which position was last updated. If the
+ * position has never been set this will return 0;
+ *
+ * @return The last time the position was updated.
+ */
+ public long getLastPositionUpdateTime() {
+ return mUpdateTime;
+ }
+
+ /**
+ * Get the id of the currently active item in the playlist.
+ *
+ * @return The id of the currently active item in the queue
+ */
+ public long getCurrentPlaylistItemIndex() {
+ return mActiveItemId;
+ }
+} \ No newline at end of file
diff --git a/media/java/android/media/Rating2.java b/media/java/android/media/Rating2.java
new file mode 100644
index 000000000000..67e5e728a4d6
--- /dev/null
+++ b/media/java/android/media/Rating2.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2018 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;
+
+import android.annotation.IntDef;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A class to encapsulate rating information used as content metadata.
+ * A rating is defined by its rating style (see {@link #RATING_HEART},
+ * {@link #RATING_THUMB_UP_DOWN}, {@link #RATING_3_STARS}, {@link #RATING_4_STARS},
+ * {@link #RATING_5_STARS} or {@link #RATING_PERCENTAGE}) and the actual rating value (which may
+ * be defined as "unrated"), both of which are defined when the rating instance is constructed
+ * through one of the factory methods.
+ * @hide
+ */
+// TODO(jaewan): Move this to updatable
+public final class Rating2 {
+ private static final String TAG = "Rating2";
+
+ private static final String KEY_STYLE = "android.media.rating2.style";
+ private static final String KEY_VALUE = "android.media.rating2.value";
+
+ /**
+ * @hide
+ */
+ @IntDef({RATING_NONE, RATING_HEART, RATING_THUMB_UP_DOWN, RATING_3_STARS, RATING_4_STARS,
+ RATING_5_STARS, RATING_PERCENTAGE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Style {}
+
+ /**
+ * @hide
+ */
+ @IntDef({RATING_3_STARS, RATING_4_STARS, RATING_5_STARS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StarStyle {}
+
+ /**
+ * Indicates a rating style is not supported. A Rating2 will never have this
+ * type, but can be used by other classes to indicate they do not support
+ * Rating2.
+ */
+ public final static int RATING_NONE = 0;
+
+ /**
+ * A rating style with a single degree of rating, "heart" vs "no heart". Can be used to
+ * indicate the content referred to is a favorite (or not).
+ */
+ public final static int RATING_HEART = 1;
+
+ /**
+ * A rating style for "thumb up" vs "thumb down".
+ */
+ public final static int RATING_THUMB_UP_DOWN = 2;
+
+ /**
+ * A rating style with 0 to 3 stars.
+ */
+ public final static int RATING_3_STARS = 3;
+
+ /**
+ * A rating style with 0 to 4 stars.
+ */
+ public final static int RATING_4_STARS = 4;
+
+ /**
+ * A rating style with 0 to 5 stars.
+ */
+ public final static int RATING_5_STARS = 5;
+
+ /**
+ * A rating style expressed as a percentage.
+ */
+ public final static int RATING_PERCENTAGE = 6;
+
+ private final static float RATING_NOT_RATED = -1.0f;
+
+ private final int mRatingStyle;
+
+ private final float mRatingValue;
+
+ private Rating2(@Style int ratingStyle, float rating) {
+ mRatingStyle = ratingStyle;
+ mRatingValue = rating;
+ }
+
+ @Override
+ public String toString() {
+ return "Rating2:style=" + mRatingStyle + " rating="
+ + (mRatingValue < 0.0f ? "unrated" : String.valueOf(mRatingValue));
+ }
+
+ /**
+ * Create an instance from bundle object, previoulsy created by {@link #toBundle()}
+ *
+ * @param bundle bundle
+ * @return new Rating2 instance
+ */
+ public static Rating2 fromBundle(Bundle bundle) {
+ return new Rating2(bundle.getInt(KEY_STYLE), bundle.getFloat(KEY_VALUE));
+ }
+
+ /**
+ * Return bundle for this object to share across the process.
+ * @return bundle of this object
+ */
+ public Bundle toBundle() {
+ Bundle bundle = new Bundle();
+ bundle.putInt(KEY_STYLE, mRatingStyle);
+ bundle.putFloat(KEY_VALUE, mRatingValue);
+ return bundle;
+ }
+
+ /**
+ * Return a Rating2 instance with no rating.
+ * Create and return a new Rating2 instance with no rating known for the given
+ * rating style.
+ * @param ratingStyle one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN},
+ * {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS},
+ * or {@link #RATING_PERCENTAGE}.
+ * @return null if an invalid rating style is passed, a new Rating2 instance otherwise.
+ */
+ public static Rating2 newUnratedRating(@Style int ratingStyle) {
+ switch(ratingStyle) {
+ case RATING_HEART:
+ case RATING_THUMB_UP_DOWN:
+ case RATING_3_STARS:
+ case RATING_4_STARS:
+ case RATING_5_STARS:
+ case RATING_PERCENTAGE:
+ return new Rating2(ratingStyle, RATING_NOT_RATED);
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Return a Rating2 instance with a heart-based rating.
+ * Create and return a new Rating2 instance with a rating style of {@link #RATING_HEART},
+ * and a heart-based rating.
+ * @param hasHeart true for a "heart selected" rating, false for "heart unselected".
+ * @return a new Rating2 instance.
+ */
+ public static Rating2 newHeartRating(boolean hasHeart) {
+ return new Rating2(RATING_HEART, hasHeart ? 1.0f : 0.0f);
+ }
+
+ /**
+ * Return a Rating2 instance with a thumb-based rating.
+ * Create and return a new Rating2 instance with a {@link #RATING_THUMB_UP_DOWN}
+ * rating style, and a "thumb up" or "thumb down" rating.
+ * @param thumbIsUp true for a "thumb up" rating, false for "thumb down".
+ * @return a new Rating2 instance.
+ */
+ public static Rating2 newThumbRating(boolean thumbIsUp) {
+ return new Rating2(RATING_THUMB_UP_DOWN, thumbIsUp ? 1.0f : 0.0f);
+ }
+
+ /**
+ * Return a Rating2 instance with a star-based rating.
+ * Create and return a new Rating2 instance with one of the star-base rating styles
+ * and the given integer or fractional number of stars. Non integer values can for instance
+ * be used to represent an average rating value, which might not be an integer number of stars.
+ * @param starRatingStyle one of {@link #RATING_3_STARS}, {@link #RATING_4_STARS},
+ * {@link #RATING_5_STARS}.
+ * @param starRating a number ranging from 0.0f to 3.0f, 4.0f or 5.0f according to
+ * the rating style.
+ * @return null if the rating style is invalid, or the rating is out of range,
+ * a new Rating2 instance otherwise.
+ */
+ public static Rating2 newStarRating(@StarStyle int starRatingStyle, float starRating) {
+ float maxRating = -1.0f;
+ switch(starRatingStyle) {
+ case RATING_3_STARS:
+ maxRating = 3.0f;
+ break;
+ case RATING_4_STARS:
+ maxRating = 4.0f;
+ break;
+ case RATING_5_STARS:
+ maxRating = 5.0f;
+ break;
+ default:
+ Log.e(TAG, "Invalid rating style (" + starRatingStyle + ") for a star rating");
+ return null;
+ }
+ if ((starRating < 0.0f) || (starRating > maxRating)) {
+ Log.e(TAG, "Trying to set out of range star-based rating");
+ return null;
+ }
+ return new Rating2(starRatingStyle, starRating);
+ }
+
+ /**
+ * Return a Rating2 instance with a percentage-based rating.
+ * Create and return a new Rating2 instance with a {@link #RATING_PERCENTAGE}
+ * rating style, and a rating of the given percentage.
+ * @param percent the value of the rating
+ * @return null if the rating is out of range, a new Rating2 instance otherwise.
+ */
+ public static Rating2 newPercentageRating(float percent) {
+ if ((percent < 0.0f) || (percent > 100.0f)) {
+ Log.e(TAG, "Invalid percentage-based rating value");
+ return null;
+ } else {
+ return new Rating2(RATING_PERCENTAGE, percent);
+ }
+ }
+
+ /**
+ * Return whether there is a rating value available.
+ * @return true if the instance was not created with {@link #newUnratedRating(int)}.
+ */
+ public boolean isRated() {
+ return mRatingValue >= 0.0f;
+ }
+
+ /**
+ * Return the rating style.
+ * @return one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN},
+ * {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS},
+ * or {@link #RATING_PERCENTAGE}.
+ */
+ @Style
+ public int getRatingStyle() {
+ return mRatingStyle;
+ }
+
+ /**
+ * Return whether the rating is "heart selected".
+ * @return true if the rating is "heart selected", false if the rating is "heart unselected",
+ * if the rating style is not {@link #RATING_HEART} or if it is unrated.
+ */
+ public boolean hasHeart() {
+ if (mRatingStyle != RATING_HEART) {
+ return false;
+ } else {
+ return (mRatingValue == 1.0f);
+ }
+ }
+
+ /**
+ * Return whether the rating is "thumb up".
+ * @return true if the rating is "thumb up", false if the rating is "thumb down",
+ * if the rating style is not {@link #RATING_THUMB_UP_DOWN} or if it is unrated.
+ */
+ public boolean isThumbUp() {
+ if (mRatingStyle != RATING_THUMB_UP_DOWN) {
+ return false;
+ } else {
+ return (mRatingValue == 1.0f);
+ }
+ }
+
+ /**
+ * Return the star-based rating value.
+ * @return a rating value greater or equal to 0.0f, or a negative value if the rating style is
+ * not star-based, or if it is unrated.
+ */
+ public float getStarRating() {
+ switch (mRatingStyle) {
+ case RATING_3_STARS:
+ case RATING_4_STARS:
+ case RATING_5_STARS:
+ if (isRated()) {
+ return mRatingValue;
+ }
+ default:
+ return -1.0f;
+ }
+ }
+
+ /**
+ * Return the percentage-based rating value.
+ * @return a rating value greater or equal to 0.0f, or a negative value if the rating style is
+ * not percentage-based, or if it is unrated.
+ */
+ public float getPercentRating() {
+ if ((mRatingStyle != RATING_PERCENTAGE) || !isRated()) {
+ return -1.0f;
+ } else {
+ return mRatingValue;
+ }
+ }
+}
diff --git a/media/java/android/media/update/MediaBrowser2Provider.java b/media/java/android/media/update/MediaBrowser2Provider.java
index 355dbc9f4aef..e48711d9fa54 100644
--- a/media/java/android/media/update/MediaBrowser2Provider.java
+++ b/media/java/android/media/update/MediaBrowser2Provider.java
@@ -23,4 +23,11 @@ import android.os.Bundle;
*/
public interface MediaBrowser2Provider extends MediaController2Provider {
void getBrowserRoot_impl(Bundle rootHints);
+
+ void subscribe_impl(String parentId, Bundle options);
+ void unsubscribe_impl(String parentId, Bundle options);
+
+ void getItem_impl(String mediaId);
+ void getChildren_impl(String parentId, int page, int pageSize, Bundle options);
+ void search_impl(String query, int page, int pageSize, Bundle extras);
}
diff --git a/media/java/android/media/update/MediaController2Provider.java b/media/java/android/media/update/MediaController2Provider.java
index 9ad5a688a01a..8f5a64310b31 100644
--- a/media/java/android/media/update/MediaController2Provider.java
+++ b/media/java/android/media/update/MediaController2Provider.java
@@ -16,10 +16,19 @@
package android.media.update;
-import android.media.MediaPlayerBase;
+import android.app.PendingIntent;
+import android.media.MediaController2.PlaybackInfo;
+import android.media.MediaItem2;
+import android.media.MediaSession2.Command;
+import android.media.MediaSession2.PlaylistParam;
+import android.media.PlaybackState2;
+import android.media.Rating2;
import android.media.SessionToken;
-import android.media.session.PlaybackState;
-import android.os.Handler;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+import java.util.List;
/**
* @hide
@@ -29,7 +38,27 @@ public interface MediaController2Provider extends TransportControlProvider {
SessionToken getSessionToken_impl();
boolean isConnected_impl();
- PlaybackState getPlaybackState_impl();
- void addPlaybackListener_impl(MediaPlayerBase.PlaybackListener listener, Handler handler);
- void removePlaybackListener_impl(MediaPlayerBase.PlaybackListener listener);
+ PendingIntent getSessionActivity_impl();
+ int getRatingType_impl();
+
+ void setVolumeTo_impl(int value, int flags);
+ void adjustVolume_impl(int direction, int flags);
+ PlaybackInfo getPlaybackInfo_impl();
+
+ void prepareFromUri_impl(Uri uri, Bundle extras);
+ void prepareFromSearch_impl(String query, Bundle extras);
+ void prepareMediaId_impl(String mediaId, Bundle extras);
+ void playFromSearch_impl(String query, Bundle extras);
+ void playFromUri_impl(String uri, Bundle extras);
+ void playFromMediaId_impl(String mediaId, Bundle extras);
+
+ void setRating_impl(Rating2 rating);
+ void sendCustomCommand_impl(Command command, Bundle args, ResultReceiver cb);
+ List<MediaItem2> getPlaylist_impl();
+
+ void removePlaylistItem_impl(MediaItem2 index);
+ void addPlaylistItem_impl(int index, MediaItem2 item);
+
+ PlaylistParam getPlaylistParam_impl();
+ PlaybackState2 getPlaybackState_impl();
}
diff --git a/media/java/android/media/update/MediaLibraryService2Provider.java b/media/java/android/media/update/MediaLibraryService2Provider.java
index 7e3444f4d9ef..dac5784181e5 100644
--- a/media/java/android/media/update/MediaLibraryService2Provider.java
+++ b/media/java/android/media/update/MediaLibraryService2Provider.java
@@ -16,9 +16,15 @@
package android.media.update;
-/**
+import android.media.MediaSession2.ControllerInfo;
+import android.os.Bundle; /**
* @hide
*/
public interface MediaLibraryService2Provider extends MediaSessionService2Provider {
// Nothing new for now
+
+ interface MediaLibrarySessionProvider extends MediaSession2Provider {
+ void notifyChildrenChanged_impl(ControllerInfo controller, String parentId, Bundle options);
+ void notifyChildrenChanged_impl(String parentId, Bundle options);
+ }
}
diff --git a/media/java/android/media/update/MediaSession2Provider.java b/media/java/android/media/update/MediaSession2Provider.java
index 402397e6916b..511686dd93f7 100644
--- a/media/java/android/media/update/MediaSession2Provider.java
+++ b/media/java/android/media/update/MediaSession2Provider.java
@@ -16,12 +16,21 @@
package android.media.update;
+import android.media.AudioAttributes;
+import android.media.MediaItem2;
import android.media.MediaPlayerBase;
+import android.media.MediaSession2;
+import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
+import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.PlaylistParam;
import android.media.SessionToken;
+import android.media.VolumeProvider;
import android.media.session.PlaybackState;
+import android.os.Bundle;
import android.os.Handler;
+import android.os.ResultReceiver;
import java.util.List;
@@ -30,11 +39,21 @@ import java.util.List;
*/
public interface MediaSession2Provider extends TransportControlProvider {
void close_impl();
- void setPlayer_impl(MediaPlayerBase player) throws IllegalArgumentException;
+ void setPlayer_impl(MediaPlayerBase player);
+ void setPlayer_impl(MediaPlayerBase player, VolumeProvider volumeProvider);
MediaPlayerBase getPlayer_impl();
SessionToken getToken_impl();
List<ControllerInfo> getConnectedControllers_impl();
void setCustomLayout_impl(ControllerInfo controller, List<CommandButton> layout);
+ void setAudioAttributes_impl(AudioAttributes attributes);
+ void setAudioFocusRequest_impl(int focusGain);
+
+ void setAllowedCommands_impl(ControllerInfo controller, CommandGroup commands);
+ void notifyMetadataChanged_impl();
+ void sendCustomCommand_impl(ControllerInfo controller, Command command, Bundle args,
+ ResultReceiver receiver);
+ void sendCustomCommand_impl(Command command, Bundle args);
+ void setPlaylist_impl(List<MediaItem2> playlist, MediaSession2.PlaylistParam param);
/**
* @hide
diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java
index fef2cbb43281..64968d66ff8a 100644
--- a/media/java/android/media/update/StaticProvider.java
+++ b/media/java/android/media/update/StaticProvider.java
@@ -17,6 +17,7 @@
package android.media.update;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.content.Context;
import android.media.IMediaSession2Callback;
import android.media.MediaBrowser2;
@@ -24,10 +25,16 @@ import android.media.MediaBrowser2.BrowserCallback;
import android.media.MediaController2;
import android.media.MediaController2.ControllerCallback;
import android.media.MediaLibraryService2;
+import android.media.MediaLibraryService2.MediaLibrarySession;
+import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
import android.media.MediaPlayerBase;
import android.media.MediaSession2;
+import android.media.MediaSession2.SessionCallback;
import android.media.MediaSessionService2;
import android.media.SessionToken;
+import android.media.VolumeProvider;
+import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider;
+import android.media.update.MediaSession2Provider.ControllerInfoProvider;
import android.util.AttributeSet;
import android.widget.MediaControlView2;
import android.widget.VideoView2;
@@ -51,8 +58,10 @@ public interface StaticProvider {
@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes);
MediaSession2Provider createMediaSession2(MediaSession2 mediaSession2, Context context,
- MediaPlayerBase player, String id, MediaSession2.SessionCallback callback);
- MediaSession2Provider.ControllerInfoProvider createMediaSession2ControllerInfoProvider(
+ MediaPlayerBase player, String id, SessionCallback callback,
+ VolumeProvider volumeProvider, int ratingType,
+ PendingIntent sessionActivity);
+ ControllerInfoProvider createMediaSession2ControllerInfoProvider(
MediaSession2.ControllerInfo instance, Context context, int uid, int pid,
String packageName, IMediaSession2Callback callback);
MediaController2Provider createMediaController2(
@@ -65,4 +74,8 @@ public interface StaticProvider {
MediaSessionService2 instance);
MediaSessionService2Provider createMediaLibraryService2(
MediaLibraryService2 instance);
+ MediaLibrarySessionProvider createMediaLibraryService2MediaLibrarySession(
+ MediaLibrarySession instance, Context context, MediaPlayerBase player, String id,
+ MediaLibrarySessionCallback callback, VolumeProvider volumeProvider, int ratingType,
+ PendingIntent sessionActivity);
}
diff --git a/media/java/android/media/update/TransportControlProvider.java b/media/java/android/media/update/TransportControlProvider.java
index 1b6b20158f58..5217a9d99d4e 100644
--- a/media/java/android/media/update/TransportControlProvider.java
+++ b/media/java/android/media/update/TransportControlProvider.java
@@ -31,7 +31,9 @@ public interface TransportControlProvider {
void skipToPrevious_impl();
void skipToNext_impl();
- PlaybackState getPlaybackState_impl();
- void addPlaybackListener_impl(MediaPlayerBase.PlaybackListener listener, Handler handler);
- void removePlaybackListener_impl(MediaPlayerBase.PlaybackListener listener);
+ void prepare_impl();
+ void fastForward_impl();
+ void rewind_impl();
+ void seekTo_impl(long pos);
+ void setCurrentPlaylistItem_impl(int index);
}
diff --git a/nfc-extras/tests/Android.mk b/nfc-extras/tests/Android.mk
index 34d65082d8d8..51396d3346e9 100644
--- a/nfc-extras/tests/Android.mk
+++ b/nfc-extras/tests/Android.mk
@@ -19,7 +19,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_JAVA_LIBRARIES := \
- android.test.runner \
+ android.test.runner.stubs \
com.android.nfc_extras \
android.test.base.stubs
diff --git a/packages/MtpDocumentsProvider/AndroidManifest.xml b/packages/MtpDocumentsProvider/AndroidManifest.xml
index 8d79f62f21d7..c0a59b3badbf 100644
--- a/packages/MtpDocumentsProvider/AndroidManifest.xml
+++ b/packages/MtpDocumentsProvider/AndroidManifest.xml
@@ -3,6 +3,7 @@
package="com.android.mtp"
android:sharedUserId="android.media">
<uses-feature android:name="android.hardware.usb.host" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.MANAGE_USB" />
<application android:label="@string/app_label">
<provider
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 55f7a0a92c88..d556db47939d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1120,8 +1120,8 @@ class SettingsProtoDumpUtil {
Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
GlobalSettingsProto.NOTIFICATION_SNOOZE_OPTIONS);
dumpSetting(s, p,
- Settings.Global.ZRAM_ENABLED,
- GlobalSettingsProto.ZRAM_ENABLED);
+ Settings.Global.ZRAM_ENABLED,
+ GlobalSettingsProto.ZRAM_ENABLED);
dumpSetting(s, p,
Settings.Global.ENABLE_SMART_REPLIES_IN_NOTIFICATIONS,
GlobalSettingsProto.ENABLE_SMART_REPLIES_IN_NOTIFICATIONS);
@@ -1129,8 +1129,14 @@ class SettingsProtoDumpUtil {
Settings.Global.SHOW_FIRST_CRASH_DIALOG,
GlobalSettingsProto.SHOW_FIRST_CRASH_DIALOG);
dumpSetting(s, p,
- Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED,
- GlobalSettingsProto.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED);
+ Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED,
+ GlobalSettingsProto.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.SHOW_RESTART_IN_CRASH_DIALOG,
+ GlobalSettingsProto.SHOW_RESTART_IN_CRASH_DIALOG);
+ dumpSetting(s, p,
+ Settings.Global.SHOW_MUTE_IN_CRASH_DIALOG,
+ GlobalSettingsProto.SHOW_MUTE_IN_CRASH_DIALOG);
}
/** Dump a single {@link SettingsState.Setting} to a proto buf */
@@ -1755,6 +1761,9 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Secure.BACKUP_MANAGER_CONSTANTS,
SecureSettingsProto.BACKUP_MANAGER_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Secure.BLUETOOTH_ON_WHILE_DRIVING,
+ SecureSettingsProto.BLUETOOTH_ON_WHILE_DRIVING);
}
private static void dumpProtoSystemSettingsLocked(
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 64b2ae6e23d3..79299aa6abcb 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -44,6 +44,7 @@
<uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
<uses-permission android:name="android.permission.MANAGE_USB" />
<uses-permission android:name="android.permission.USE_RESERVED_DISK" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<!-- System tool permissions granted to the shell. -->
<uses-permission android:name="android.permission.REAL_GET_TASKS" />
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4b2e62c80281..0b6e11bf8638 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -89,6 +89,7 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" />
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+ <uses-permission android:name="android.permission.START_ACTIVITY_AS_CALLER" />
<uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
<uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
@@ -555,6 +556,22 @@
</intent-filter>
</activity>
+ <activity android:name=".chooser.ChooserActivity"
+ android:theme="@*android:style/Theme.NoDisplay"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true"
+ android:documentLaunchMode="never"
+ android:relinquishTaskIdentity="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ android:process=":ui"
+ android:visibleToInstantApps="true">
+ <intent-filter>
+ <action android:name="android.intent.action.CHOOSER_UI" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.VOICE" />
+ </intent-filter>
+ </activity>
+
<!-- Doze with notifications, run in main sysui process for every user -->
<service
android:name=".doze.DozeService"
diff --git a/packages/SystemUI/res/layout/output_chooser.xml b/packages/SystemUI/res/layout/output_chooser.xml
index b96f44750a0c..b9f7b152ecac 100644
--- a/packages/SystemUI/res/layout/output_chooser.xml
+++ b/packages/SystemUI/res/layout/output_chooser.xml
@@ -15,47 +15,56 @@
limitations under the License.
-->
<!-- extends LinearLayout -->
-<com.android.systemui.volume.OutputChooserLayout
+<com.android.systemui.HardwareUiLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:sysui="http://schemas.android.com/apk/res-auto"
- android:id="@+id/output_chooser"
- android:minWidth="320dp"
- android:minHeight="320dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical"
- android:padding="20dp" >
-
- <TextView
- android:id="@+id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textDirection="locale"
- android:textAppearance="@style/TextAppearance.QS.DetailHeader"
- android:layout_marginBottom="20dp" />
-
- <com.android.systemui.qs.AutoSizingList
- android:id="@android:id/list"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_marginBottom="0dp"
+ android:clipToPadding="false"
+ android:theme="@style/qs_theme"
+ android:clipChildren="false">
+ <com.android.systemui.volume.OutputChooserLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:sysui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/output_chooser"
+ android:layout_width="@dimen/output_chooser_panel_width"
+ android:layout_height="@dimen/output_chooser_panel_width"
+ android:layout_gravity="center_vertical|end"
android:orientation="vertical"
- sysui:itemHeight="@dimen/qs_detail_item_height"
- style="@style/AutoSizingList"/>
-
- <LinearLayout
- android:id="@android:id/empty"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:gravity="center"
- android:orientation="vertical">
+ android:translationZ="8dp"
+ android:padding="20dp" >
<TextView
- android:id="@+id/empty_text"
+ android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textDirection="locale"
- android:layout_marginTop="20dp"
- android:textAppearance="@style/TextAppearance.QS.DetailEmpty"/>
- </LinearLayout>
-</com.android.systemui.volume.OutputChooserLayout>
+ android:textAppearance="@style/TextAppearance.QS.DetailHeader"
+ android:layout_marginBottom="20dp" />
+
+ <com.android.systemui.qs.AutoSizingList
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ sysui:itemHeight="@dimen/qs_detail_item_height"
+ style="@style/AutoSizingList"/>
+
+ <LinearLayout
+ android:id="@android:id/empty"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/empty_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textDirection="locale"
+ android:layout_marginTop="20dp"
+ android:textAppearance="@style/TextAppearance.QS.DetailEmpty"/>
+ </LinearLayout>
+ </com.android.systemui.volume.OutputChooserLayout>
+</com.android.systemui.HardwareUiLayout>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 5108f58dc832..117cd14f4463 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -41,6 +41,7 @@
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:background="@drawable/rounded_bg_full"
+ android:translationZ="8dp"
android:orientation="horizontal" >
<!-- volume rows added and removed here! :-) -->
</LinearLayout>
@@ -57,6 +58,7 @@
android:background="@drawable/rounded_bg_full"
android:gravity="center"
android:layout_gravity="end"
+ android:translationZ="8dp"
android:orientation="vertical" >
<TextView
diff --git a/packages/SystemUI/res/layout/wireless_charging_layout.xml b/packages/SystemUI/res/layout/wireless_charging_layout.xml
new file mode 100644
index 000000000000..113282b8d3fe
--- /dev/null
+++ b/packages/SystemUI/res/layout/wireless_charging_layout.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- Circle animation -->
+ <com.android.systemui.charging.WirelessChargingView
+ android:id="@+id/wireless_charging_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:elevation="4dp"/>
+
+ <!-- Text inside circle -->
+ <LinearLayout
+ android:id="@+id/wireless_charging_text_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/wireless_charging_percentage"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:textSize="32sp"
+ android:textColor="?attr/wallpaperTextColor"/>
+
+ <TextView
+ android:id="@+id/wireless_charging_secondary_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:textColor="?attr/wallpaperTextColor"/>
+ </LinearLayout>
+
+</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b749a18a8c68..a3b58cd319ef 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -267,6 +267,8 @@
<dimen name="volume_dialog_panel_width">120dp</dimen>
+ <dimen name="output_chooser_panel_width">320dp</dimen>
+
<!-- Gravity for the notification panel -->
<integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top -->
@@ -893,4 +895,12 @@
<dimen name="fingerprint_dialog_icon_size">44dp</dimen>
<dimen name="fingerprint_dialog_fp_icon_size">60dp</dimen>
<dimen name="fingerprint_dialog_animation_translation_offset">350dp</dimen>
+
+ <!-- WirelessCharging Animation values -->
+ <!-- Starting text size of batteryLevel for wireless charging animation -->
+ <dimen name="config_batteryLevelTextSizeStart" format="float">5.0</dimen>
+ <!-- Ending text size of batteryLevel for wireless charging animation -->
+ <dimen name="config_batteryLevelTextSizeEnd" format="float">32.0</dimen>
+ <!-- Wireless Charging battery level text animation duration -->
+ <integer name="config_batteryLevelTextAnimationDuration">400</integer>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
index ca34345d923b..948178802003 100644
--- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
@@ -58,6 +58,7 @@ public class HardwareUiLayout extends FrameLayout implements Tunable {
private boolean mRoundedDivider;
private int mRotation = ROTATION_NONE;
private boolean mRotatedBackground;
+ private boolean mSwapOrientation = true;
public HardwareUiLayout(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -145,6 +146,10 @@ public class HardwareUiLayout extends FrameLayout implements Tunable {
updateRotation();
}
+ public void setSwapOrientation(boolean swapOrientation) {
+ mSwapOrientation = swapOrientation;
+ }
+
private void updateRotation() {
int rotation = RotationUtils.getRotation(getContext());
if (rotation != mRotation) {
@@ -173,7 +178,9 @@ public class HardwareUiLayout extends FrameLayout implements Tunable {
if (to == ROTATION_SEASCAPE) {
swapOrder(linearLayout);
}
- linearLayout.setOrientation(LinearLayout.HORIZONTAL);
+ if (mSwapOrientation) {
+ linearLayout.setOrientation(LinearLayout.HORIZONTAL);
+ }
swapDimens(this.mChild);
}
} else {
@@ -184,7 +191,9 @@ public class HardwareUiLayout extends FrameLayout implements Tunable {
if (from == ROTATION_SEASCAPE) {
swapOrder(linearLayout);
}
- linearLayout.setOrientation(LinearLayout.VERTICAL);
+ if (mSwapOrientation) {
+ linearLayout.setOrientation(LinearLayout.VERTICAL);
+ }
swapDimens(mChild);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
new file mode 100644
index 000000000000..348855bb0440
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2018 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.charging;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+
+/**
+ * A WirelessChargingAnimation is a view containing view + animation for wireless charging.
+ * @hide
+ */
+public class WirelessChargingAnimation {
+
+ public static final long DURATION = 1400;
+ private static final String TAG = "WirelessChargingView";
+ private static final boolean LOCAL_LOGV = false;
+
+ private final WirelessChargingView mCurrentWirelessChargingView;
+ private static WirelessChargingView mPreviousWirelessChargingView;
+
+ /**
+ * Constructs an empty WirelessChargingAnimation object. If looper is null,
+ * Looper.myLooper() is used. Must set
+ * {@link WirelessChargingAnimation#mCurrentWirelessChargingView}
+ * before calling {@link #show} - can be done through {@link #makeWirelessChargingAnimation}.
+ * @hide
+ */
+ public WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper, int
+ batteryLevel) {
+ mCurrentWirelessChargingView = new WirelessChargingView(context, looper,
+ batteryLevel);
+ }
+
+ /**
+ * Creates a wireless charging animation object populated with next view.
+ * @hide
+ */
+ public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context,
+ @Nullable Looper looper, int batteryLevel) {
+ return new WirelessChargingAnimation(context, looper, batteryLevel);
+ }
+
+ /**
+ * Show the view for the specified duration.
+ */
+ public void show() {
+ if (mCurrentWirelessChargingView == null ||
+ mCurrentWirelessChargingView.mNextView == null) {
+ throw new RuntimeException("setView must have been called");
+ }
+
+ if (mPreviousWirelessChargingView != null) {
+ mPreviousWirelessChargingView.hide(0);
+ }
+
+ mPreviousWirelessChargingView = mCurrentWirelessChargingView;
+ mCurrentWirelessChargingView.show();
+ mCurrentWirelessChargingView.hide(DURATION);
+ }
+
+ private static class WirelessChargingView {
+ private static final int SHOW = 0;
+ private static final int HIDE = 1;
+
+ private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+ private final Handler mHandler;
+
+ private int mGravity;
+
+ private View mView;
+ private View mNextView;
+ private WindowManager mWM;
+
+ public WirelessChargingView(Context context, @Nullable Looper looper, int batteryLevel) {
+ mNextView = new WirelessChargingLayout(context, batteryLevel);
+ mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER;
+
+ final WindowManager.LayoutParams params = mParams;
+ params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.format = PixelFormat.TRANSLUCENT;
+
+ params.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
+ params.setTitle("Charging Animation");
+ params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+
+ params.dimAmount = .3f;
+
+ if (looper == null) {
+ // Use Looper.myLooper() if looper is not specified.
+ looper = Looper.myLooper();
+ if (looper == null) {
+ throw new RuntimeException(
+ "Can't display wireless animation on a thread that has not called "
+ + "Looper.prepare()");
+ }
+ }
+
+ mHandler = new Handler(looper, null) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SHOW: {
+ handleShow();
+ break;
+ }
+ case HIDE: {
+ handleHide();
+ // Don't do this in handleHide() because it is also invoked by
+ // handleShow()
+ mNextView = null;
+ break;
+ }
+ }
+ }
+ };
+ }
+
+ public void show() {
+ if (LOCAL_LOGV) Log.v(TAG, "SHOW: " + this);
+ mHandler.obtainMessage(SHOW).sendToTarget();
+ }
+
+ public void hide(long duration) {
+ if (LOCAL_LOGV) Log.v(TAG, "HIDE: " + this);
+ mHandler.sendMessageDelayed(Message.obtain(mHandler, HIDE), duration);
+ }
+
+ private void handleShow() {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView + " mNextView="
+ + mNextView);
+ }
+
+ if (mView != mNextView) {
+ // remove the old view if necessary
+ handleHide();
+ mView = mNextView;
+ Context context = mView.getContext().getApplicationContext();
+ String packageName = mView.getContext().getOpPackageName();
+ if (context == null) {
+ context = mView.getContext();
+ }
+ mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ // We can resolve the Gravity here by using the Locale for getting
+ // the layout direction
+ final Configuration config = mView.getContext().getResources().getConfiguration();
+ final int gravity = Gravity.getAbsoluteGravity(mGravity,
+ config.getLayoutDirection());
+ mParams.gravity = gravity;
+ if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
+ mParams.horizontalWeight = 1.0f;
+ }
+ if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
+ mParams.verticalWeight = 1.0f;
+ }
+ mParams.packageName = packageName;
+ mParams.hideTimeoutMilliseconds = DURATION;
+
+ if (mView.getParent() != null) {
+ if (LOCAL_LOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
+ mWM.removeView(mView);
+ }
+ if (LOCAL_LOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
+
+ try {
+ mWM.addView(mView, mParams);
+ } catch (WindowManager.BadTokenException e) {
+ Slog.d(TAG, "Unable to add wireless charging view. " + e);
+ }
+ }
+ }
+
+ private void handleHide() {
+ if (LOCAL_LOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
+ if (mView != null) {
+ if (mView.getParent() != null) {
+ if (LOCAL_LOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
+ mWM.removeViewImmediate(mView);
+ }
+
+ mView = null;
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
new file mode 100644
index 000000000000..c78ea56524cb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 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.charging;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+import java.text.NumberFormat;
+
+/**
+ * @hide
+ */
+public class WirelessChargingLayout extends FrameLayout {
+ private final static int UNKNOWN_BATTERY_LEVEL = -1;
+
+ public WirelessChargingLayout(Context context) {
+ super(context);
+ init(context, null);
+ }
+
+ public WirelessChargingLayout(Context context, int batterylLevel) {
+ super(context);
+ init(context, null, batterylLevel);
+ }
+
+ public WirelessChargingLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs);
+ }
+
+ private void init(Context c, AttributeSet attrs) {
+ init(c, attrs, -1);
+ }
+
+ private void init(Context c, AttributeSet attrs, int batteryLevel) {
+ final int mBatteryLevel = batteryLevel;
+
+ inflate(c, R.layout.wireless_charging_layout, this);
+
+ // where the circle animation occurs:
+ final WirelessChargingView mChargingView = findViewById(R.id.wireless_charging_view);
+
+ // amount of battery:
+ final TextView mPercentage = findViewById(R.id.wireless_charging_percentage);
+
+ // (optional) time until full charge if available
+ final TextView mSecondaryText = findViewById(R.id.wireless_charging_secondary_text);
+
+ if (batteryLevel != UNKNOWN_BATTERY_LEVEL) {
+ mPercentage.setText(NumberFormat.getPercentInstance().format(mBatteryLevel / 100f));
+
+ ValueAnimator animator = ObjectAnimator.ofFloat(mPercentage, "textSize",
+ getContext().getResources().getFloat(R.dimen.config_batteryLevelTextSizeStart),
+ getContext().getResources().getFloat(R.dimen.config_batteryLevelTextSizeEnd));
+
+ animator.setDuration((long) getContext().getResources().getInteger(
+ R.integer.config_batteryLevelTextAnimationDuration));
+ animator.start();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java
new file mode 100644
index 000000000000..f5edf5216fa2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2018 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.charging;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.R;
+
+final class WirelessChargingView extends View {
+
+ private Interpolator mInterpolator;
+ private float mPathGone;
+ private float mInterpolatedPathGone;
+ private long mAnimationStartTime;
+ private long mStartSpinCircleAnimationTime;
+ private long mAnimationOffset = 500;
+ private long mTotalAnimationDuration = WirelessChargingAnimation.DURATION - mAnimationOffset;
+ private long mExpandingCircle = (long) (mTotalAnimationDuration * .9);
+ private long mSpinCircleAnimationTime = mTotalAnimationDuration - mExpandingCircle;
+
+ private boolean mFinishedAnimatingSpinningCircles = false;
+
+ private int mStartAngle = -90;
+ private int mNumSmallCircles = 20;
+ private int mSmallCircleRadius = 10;
+
+ private int mMainCircleStartRadius = 100;
+ private int mMainCircleEndRadius = 230;
+ private int mMainCircleCurrentRadius = mMainCircleStartRadius;
+
+ private int mCenterX;
+ private int mCenterY;
+
+ private Paint mPaint;
+ private Context mContext;
+
+ public WirelessChargingView(Context context) {
+ super(context);
+ init(context, null);
+ }
+
+ public WirelessChargingView(Context context, AttributeSet attr) {
+ super(context, attr);
+ init(context, attr);
+ }
+
+ public WirelessChargingView(Context context, AttributeSet attr, int styleAttr) {
+ super(context, attr, styleAttr);
+ init(context, attr);
+ }
+
+ public void init(Context context, AttributeSet attr) {
+ mContext = context;
+ setupPaint();
+ mInterpolator = new DecelerateInterpolator();
+ }
+
+ private void setupPaint() {
+ mPaint = new Paint();
+ mPaint.setColor(Utils.getColorAttr(mContext, R.attr.wallpaperTextColor));
+ }
+
+ @Override
+ protected void onDraw(final Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (mAnimationStartTime == 0) {
+ mAnimationStartTime = System.currentTimeMillis();
+ }
+
+ updateDrawingParameters();
+ drawCircles(canvas);
+
+ if (!mFinishedAnimatingSpinningCircles) {
+ invalidate();
+ }
+ }
+
+ /**
+ * Draws a larger circle of radius {@link WirelessChargingView#mMainCircleEndRadius} composed of
+ * {@link WirelessChargingView#mNumSmallCircles} smaller circles
+ * @param canvas
+ */
+ private void drawCircles(Canvas canvas) {
+ mCenterX = canvas.getWidth() / 2;
+ mCenterY = canvas.getHeight() / 2;
+
+ // angleOffset makes small circles look like they're moving around the main circle
+ float angleOffset = mPathGone * 10;
+
+ // draws mNumSmallCircles to compose a larger, main circle
+ for (int circle = 0; circle < mNumSmallCircles; circle++) {
+ double angle = ((mStartAngle + angleOffset) * Math.PI / 180) + (circle * ((2 * Math.PI)
+ / mNumSmallCircles));
+
+ int x = (int) (mCenterX + Math.cos(angle) * (mMainCircleCurrentRadius +
+ mSmallCircleRadius));
+ int y = (int) (mCenterY + Math.sin(angle) * (mMainCircleCurrentRadius +
+ mSmallCircleRadius));
+
+ canvas.drawCircle(x, y, mSmallCircleRadius, mPaint);
+ }
+
+ if (mMainCircleCurrentRadius >= mMainCircleEndRadius && !isSpinCircleAnimationStarted()) {
+ mStartSpinCircleAnimationTime = System.currentTimeMillis();
+ }
+
+ if (isSpinAnimationFinished()) {
+ mFinishedAnimatingSpinningCircles = true;
+ }
+ }
+
+ private boolean isSpinCircleAnimationStarted() {
+ return mStartSpinCircleAnimationTime != 0;
+ }
+
+ private boolean isSpinAnimationFinished() {
+ return isSpinCircleAnimationStarted() && System.currentTimeMillis() -
+ mStartSpinCircleAnimationTime > mSpinCircleAnimationTime;
+ }
+
+ private void updateDrawingParameters() {
+ mPathGone = getPathGone(System.currentTimeMillis());
+ mInterpolatedPathGone = mInterpolator.getInterpolation(mPathGone);
+
+ if (mPathGone < 1.0f) {
+ mMainCircleCurrentRadius = mMainCircleStartRadius + (int) (mInterpolatedPathGone *
+ (mMainCircleEndRadius - mMainCircleStartRadius));
+ } else {
+ mMainCircleCurrentRadius = mMainCircleEndRadius;
+ }
+ }
+
+ /**
+ * @return decimal depicting how far along the creation of the larger circle (of circles) is
+ * For values < 1.0, the larger circle is being drawn
+ * For values > 1.0 the larger circle has been drawn and further animation can occur
+ */
+ private float getPathGone(long now) {
+ return (float) (now - mAnimationStartTime) / (mExpandingCircle);
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java b/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java
new file mode 100644
index 000000000000..085ece75362d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 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.chooser;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.systemui.R;
+
+import java.lang.Thread;
+import java.util.ArrayList;
+
+public final class ChooserActivity extends Activity {
+
+ private static final String TAG = "ChooserActivity";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ChooserHelper.onChoose(this);
+ finish();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java b/packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java
new file mode 100644
index 000000000000..ac22568f7368
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 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.chooser;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.systemui.R;
+
+public class ChooserHelper {
+
+ private static final String TAG = "ChooserHelper";
+
+ static void onChoose(Activity activity) {
+ final Intent thisIntent = activity.getIntent();
+ final Bundle thisExtras = thisIntent.getExtras();
+ final Intent chosenIntent = thisIntent.getParcelableExtra(Intent.EXTRA_INTENT);
+ final Bundle options = thisIntent.getParcelableExtra(ActivityManager.EXTRA_OPTIONS);
+ final IBinder permissionToken =
+ thisExtras.getBinder(ActivityManager.EXTRA_PERMISSION_TOKEN);
+ final boolean ignoreTargetSecurity =
+ thisIntent.getBooleanExtra(ActivityManager.EXTRA_IGNORE_TARGET_SECURITY, false);
+ final int userId = thisIntent.getIntExtra(Intent.EXTRA_USER_ID, -1);
+ activity.startActivityAsCaller(
+ chosenIntent, options, permissionToken, ignoreTargetSecurity, userId);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
index f06cda0f787a..aa085626b6c2 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -14,6 +14,10 @@
package com.android.systemui.globalactions;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dependency;
import com.android.systemui.SysUiServiceProvider;
@@ -25,10 +29,6 @@ import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.ExtensionController.Extension;
-import android.content.Context;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-
public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager {
private GlobalActions mPlugin;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 00bc62e9f1d6..79e9f7b45d8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -89,6 +89,7 @@ public class CommandQueue extends IStatusBar.Stub {
private static final int MSG_FINGERPRINT_HELP = 41 << MSG_SHIFT;
private static final int MSG_FINGERPRINT_ERROR = 42 << MSG_SHIFT;
private static final int MSG_FINGERPRINT_HIDE = 43 << MSG_SHIFT;
+ private static final int MSG_SHOW_CHARGING_ANIMATION = 44 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -150,6 +151,8 @@ public class CommandQueue extends IStatusBar.Stub {
default void handleShowGlobalActionsMenu() { }
default void handleShowShutdownUi(boolean isReboot, String reason) { }
+ default void showChargingAnimation(int batteryLevel) { }
+
default void onRotationProposal(int rotation, boolean isValid) { }
default void showFingerprintDialog(Bundle bundle, IFingerprintDialogReceiver receiver) { }
@@ -474,6 +477,13 @@ public class CommandQueue extends IStatusBar.Stub {
}
@Override
+ public void showChargingAnimation(int batteryLevel) {
+ mHandler.removeMessages(MSG_SHOW_CHARGING_ANIMATION);
+ mHandler.obtainMessage(MSG_SHOW_CHARGING_ANIMATION, batteryLevel, 0)
+ .sendToTarget();
+ }
+
+ @Override
public void onProposedRotationChanged(int rotation, boolean isValid) {
synchronized (mLock) {
mHandler.removeMessages(MSG_ROTATION_PROPOSAL);
@@ -751,6 +761,12 @@ public class CommandQueue extends IStatusBar.Stub {
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).hideFingerprintDialog();
}
+ break;
+ case MSG_SHOW_CHARGING_ANIMATION:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).showChargingAnimation(msg.arg1);
+ }
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 64df92c3bd51..a4c17e3681b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -22,6 +22,7 @@ import android.app.RemoteInput;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
+import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.Log;
@@ -31,6 +32,7 @@ import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.NotificationColorUtil;
@@ -42,6 +44,7 @@ import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.RemoteInputView;
+import com.android.systemui.statusbar.policy.SmartReplyView;
/**
* A frame layout containing the actual payload of the notification, including the contracted,
@@ -72,6 +75,7 @@ public class NotificationContentView extends FrameLayout {
private RemoteInputView mExpandedRemoteInput;
private RemoteInputView mHeadsUpRemoteInput;
+ private SmartReplyView mExpandedSmartReplyView;
private NotificationViewWrapper mContractedWrapper;
private NotificationViewWrapper mExpandedWrapper;
@@ -1125,7 +1129,7 @@ public class NotificationContentView extends FrameLayout {
if (mAmbientChild != null) {
mAmbientWrapper.onContentUpdated(entry.row);
}
- applyRemoteInput(entry);
+ applyRemoteInputAndSmartReply(entry);
updateLegacy();
mForceSelectNextLayout = true;
setDark(mDark, false /* animate */, 0 /* delay */);
@@ -1157,20 +1161,34 @@ public class NotificationContentView extends FrameLayout {
}
}
- private void applyRemoteInput(final NotificationData.Entry entry) {
+ private void applyRemoteInputAndSmartReply(final NotificationData.Entry entry) {
if (mRemoteInputController == null) {
return;
}
+ boolean enableSmartReplies = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ENABLE_SMART_REPLIES_IN_NOTIFICATIONS, 0) != 0;
+
boolean hasRemoteInput = false;
+ RemoteInput remoteInputWithChoices = null;
+ PendingIntent pendingIntentWithChoices = null;
Notification.Action[] actions = entry.notification.getNotification().actions;
if (actions != null) {
for (Notification.Action a : actions) {
if (a.getRemoteInputs() != null) {
for (RemoteInput ri : a.getRemoteInputs()) {
- if (ri.getAllowFreeFormInput()) {
+ boolean showRemoteInputView = ri.getAllowFreeFormInput();
+ boolean showSmartReplyView = enableSmartReplies && ri.getChoices() != null
+ && ri.getChoices().length > 0;
+ if (showRemoteInputView) {
hasRemoteInput = true;
+ }
+ if (showSmartReplyView) {
+ remoteInputWithChoices = ri;
+ pendingIntentWithChoices = a.actionIntent;
+ }
+ if (showRemoteInputView || showSmartReplyView) {
break;
}
}
@@ -1178,6 +1196,11 @@ public class NotificationContentView extends FrameLayout {
}
}
+ applyRemoteInput(entry, hasRemoteInput);
+ applySmartReplyView(remoteInputWithChoices, pendingIntentWithChoices);
+ }
+
+ private void applyRemoteInput(NotificationData.Entry entry, boolean hasRemoteInput) {
View bigContentView = mExpandedChild;
if (bigContentView != null) {
mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasRemoteInput,
@@ -1274,6 +1297,40 @@ public class NotificationContentView extends FrameLayout {
return null;
}
+ private void applySmartReplyView(RemoteInput remoteInput, PendingIntent pendingIntent) {
+ mExpandedSmartReplyView = mExpandedChild == null ?
+ null : applySmartReplyView(mExpandedChild, remoteInput, pendingIntent);
+ }
+
+ private SmartReplyView applySmartReplyView(
+ View view, RemoteInput remoteInput, PendingIntent pendingIntent) {
+ View smartReplyContainerCandidate = view.findViewById(
+ com.android.internal.R.id.smart_reply_container);
+ if (!(smartReplyContainerCandidate instanceof LinearLayout)) {
+ return null;
+ }
+ LinearLayout smartReplyContainer = (LinearLayout) smartReplyContainerCandidate;
+ if (remoteInput == null || pendingIntent == null) {
+ smartReplyContainer.setVisibility(View.GONE);
+ return null;
+ }
+ SmartReplyView smartReplyView = null;
+ if (smartReplyContainer.getChildCount() == 0) {
+ smartReplyView = SmartReplyView.inflate(mContext, smartReplyContainer);
+ smartReplyContainer.addView(smartReplyView);
+ } else if (smartReplyContainer.getChildCount() == 1) {
+ View child = smartReplyContainer.getChildAt(0);
+ if (child instanceof SmartReplyView) {
+ smartReplyView = (SmartReplyView) child;
+ }
+ }
+ if (smartReplyView != null) {
+ smartReplyView.setRepliesFromRemoteInput(remoteInput, pendingIntent);
+ smartReplyContainer.setVisibility(View.VISIBLE);
+ }
+ return smartReplyView;
+ }
+
public void closeRemoteInput() {
if (mHeadsUpRemoteInput != null) {
mHeadsUpRemoteInput.close();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 368b36bfd8e1..dc51b1c3865d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -60,6 +60,7 @@ import android.view.IRotationWatcher.Stub;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -830,10 +831,13 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
// window in response to the orientation change.
Handler h = getView().getHandler();
Message msg = Message.obtain(h, () -> {
- // If the screen rotation changes while locked, update lock rotation to flow with
+
+ // If the screen rotation changes while locked, potentially update lock to flow with
// new screen rotation and hide any showing suggestions.
if (mRotationLockController.isRotationLocked()) {
- mRotationLockController.setRotationLockedAtAngle(true, rotation);
+ if (shouldOverrideUserLockPrefs(rotation)) {
+ mRotationLockController.setRotationLockedAtAngle(true, rotation);
+ }
setRotateSuggestionButtonState(false, true);
}
@@ -845,6 +849,12 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
msg.setAsynchronous(true);
h.sendMessageAtFrontOfQueue(msg);
}
+
+ private boolean shouldOverrideUserLockPrefs(final int rotation) {
+ // Only override user prefs when returning to portrait.
+ // Don't let apps that force landscape or 180 alter user lock.
+ return rotation == Surface.ROTATION_0;
+ }
};
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 3b783c2a079a..d13ecaeb11b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -85,6 +85,7 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
@@ -138,6 +139,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.ActivityStarterDelegate;
import com.android.systemui.AutoReinflateContainer;
+import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.DemoMode;
import com.android.systemui.Dependency;
import com.android.systemui.EventLogTags;
@@ -1419,7 +1421,6 @@ public class StatusBar extends SystemUI implements DemoMode,
mQSPanel.clickTile(tile);
}
-
private void updateClearAll() {
if (!mClearAllEnabled) {
return;
@@ -2422,6 +2423,18 @@ public class StatusBar extends SystemUI implements DemoMode,
mask, fullscreenStackBounds, dockedStackBounds, sbModeChanged, mStatusBarMode);
}
+ @Override
+ public void showChargingAnimation(int batteryLevel) {
+ if (mDozing) {
+ // ambient
+ } else if (mKeyguardManager.isKeyguardLocked()) {
+ // lockscreen
+ } else {
+ WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
+ batteryLevel).show();
+ }
+ }
+
void touchAutoHide() {
// update transient bar autohide
if (mStatusBarMode == MODE_SEMI_TRANSPARENT || (mNavigationBar != null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index c377feb0b2e3..b63c1da59cba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -137,6 +137,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
results);
+ RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT);
mEditText.setEnabled(false);
mSendButton.setVisibility(INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 1dcdf6325809..2d829af9cda7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -52,6 +52,7 @@ public class SmartReplyView extends LinearLayout {
results.putString(remoteInput.getResultKey(), choice.toString());
Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
RemoteInput.addResultsToIntent(new RemoteInput[]{remoteInput}, intent, results);
+ RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE);
try {
pendingIntent.send(context, 0, intent);
} catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
index e0af9baf0bb0..e3c8503077d8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
@@ -22,6 +22,7 @@ import static android.support.v7.media.MediaRouter.UNSELECT_REASON_DISCONNECTED;
import static com.android.settingslib.bluetooth.Utils.getBtClassDrawableWithDescription;
+import android.app.Dialog;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
@@ -30,6 +31,8 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.net.wifi.WifiManager;
@@ -43,12 +46,16 @@ import android.support.v7.media.MediaRouter;
import android.telecom.TelecomManager;
import android.util.Log;
import android.util.Pair;
+import android.view.Window;
+import android.view.WindowManager;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.systemui.Dependency;
+import com.android.systemui.HardwareUiLayout;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.statusbar.policy.BluetoothController;
import java.io.IOException;
@@ -58,7 +65,7 @@ import java.util.Collection;
import java.util.Comparator;
import java.util.List;
-public class OutputChooserDialog extends SystemUIDialog
+public class OutputChooserDialog extends Dialog
implements DialogInterface.OnDismissListener, OutputChooserLayout.Callback {
private static final String TAG = Util.logTag(OutputChooserDialog.class);
@@ -82,9 +89,11 @@ public class OutputChooserDialog extends SystemUIDialog
private Drawable mTvIcon;
private Drawable mSpeakerIcon;
private Drawable mSpeakerGroupIcon;
+ private HardwareUiLayout mHardwareLayout;
+ private final VolumeDialogController mController;
public OutputChooserDialog(Context context, MediaRouterWrapper router) {
- super(context);
+ super(context, com.android.systemui.R.style.qs_theme);
mContext = context;
mBluetoothController = Dependency.get(BluetoothController.class);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
@@ -98,6 +107,22 @@ public class OutputChooserDialog extends SystemUIDialog
final IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
context.registerReceiver(mReceiver, filter);
+
+ mController = Dependency.get(VolumeDialogController.class);
+
+ // Window initialization
+ Window window = getWindow();
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+ window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
+ window.addFlags(
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
}
protected void setIsInCall(boolean inCall) {
@@ -112,6 +137,9 @@ public class OutputChooserDialog extends SystemUIDialog
setOnDismissListener(this::onDismiss);
mView = findViewById(R.id.output_chooser);
+ mHardwareLayout = HardwareUiLayout.get(mView);
+ mHardwareLayout.setOutsideTouchListener(view -> dismiss());
+ mHardwareLayout.setSwapOrientation(false);
mView.setCallback(this);
if (mIsInCall) {
@@ -151,6 +179,7 @@ public class OutputChooserDialog extends SystemUIDialog
MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
}
mBluetoothController.addCallback(mCallback);
+ mController.addCallback(mControllerCallbackH, mHandler);
isAttached = true;
}
@@ -158,6 +187,7 @@ public class OutputChooserDialog extends SystemUIDialog
public void onDetachedFromWindow() {
isAttached = false;
mRouter.removeCallback(mRouterCallback);
+ mController.removeCallback(mControllerCallbackH);
mBluetoothController.removeCallback(mCallback);
super.onDetachedFromWindow();
}
@@ -169,6 +199,38 @@ public class OutputChooserDialog extends SystemUIDialog
}
@Override
+ public void show() {
+ super.show();
+ mHardwareLayout.setTranslationX(getAnimTranslation());
+ mHardwareLayout.setAlpha(0);
+ mHardwareLayout.animate()
+ .alpha(1)
+ .translationX(0)
+ .setDuration(300)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .withEndAction(() -> getWindow().getDecorView().requestAccessibilityFocus())
+ .start();
+ }
+
+ @Override
+ public void dismiss() {
+ mHardwareLayout.setTranslationX(0);
+ mHardwareLayout.setAlpha(1);
+ mHardwareLayout.animate()
+ .alpha(0)
+ .translationX(getAnimTranslation())
+ .setDuration(300)
+ .withEndAction(() -> super.dismiss())
+ .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
+ .start();
+ }
+
+ private float getAnimTranslation() {
+ return getContext().getResources().getDimension(
+ com.android.systemui.R.dimen.output_chooser_panel_width) / 2;
+ }
+
+ @Override
public void onDetailItemClick(OutputChooserLayout.Item item) {
if (item == null || item.tag == null) return;
if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
@@ -416,4 +478,41 @@ public class OutputChooserDialog extends SystemUIDialog
}
}
};
+
+ private final VolumeDialogController.Callbacks mControllerCallbackH
+ = new VolumeDialogController.Callbacks() {
+ @Override
+ public void onShowRequested(int reason) {
+ dismiss();
+ }
+
+ @Override
+ public void onDismissRequested(int reason) {}
+
+ @Override
+ public void onScreenOff() {
+ dismiss();
+ }
+
+ @Override
+ public void onStateChanged(VolumeDialogController.State state) {}
+
+ @Override
+ public void onLayoutDirectionChanged(int layoutDirection) {}
+
+ @Override
+ public void onConfigurationChanged() {}
+
+ @Override
+ public void onShowVibrateHint() {}
+
+ @Override
+ public void onShowSilentHint() {}
+
+ @Override
+ public void onShowSafetyWarning(int flags) {}
+
+ @Override
+ public void onAccessibilityModeChanged(Boolean showA11yStream) {}
+ };
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java
index 1c9cbc139504..368194e57b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java
@@ -25,6 +25,7 @@ import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Configuration;
import android.util.AttributeSet;
+import android.util.Slog;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
@@ -127,17 +128,26 @@ public class VolumeUiLayout extends FrameLayout {
rotate(mChild, from, to, true);
ViewGroup rows = mChild.findViewById(R.id.volume_dialog_rows);
rotate(rows, from, to, true);
+ swapOrientation((LinearLayout) rows);
int rowCount = rows.getChildCount();
for (int i = 0; i < rowCount; i++) {
- View child = rows.getChildAt(i);
+ View row = rows.getChildAt(i);
if (to == ROTATION_SEASCAPE) {
- rotateSeekBars(to, 0);
+ rotateSeekBars(row, to, 180);
} else if (to == ROTATION_LANDSCAPE) {
- rotateSeekBars(to, 180);
+ rotateSeekBars(row, to, 0);
} else {
- rotateSeekBars(to, 270);
+ rotateSeekBars(row, to, 270);
}
- rotate(child, from, to, true);
+ rotate(row, from, to, true);
+ }
+ }
+
+ private void swapOrientation(LinearLayout layout) {
+ if(layout.getOrientation() == LinearLayout.HORIZONTAL) {
+ layout.setOrientation(LinearLayout.VERTICAL);
+ } else {
+ layout.setOrientation(LinearLayout.HORIZONTAL);
}
}
@@ -152,13 +162,13 @@ public class VolumeUiLayout extends FrameLayout {
v.setLayoutParams(params);
}
- private void rotateSeekBars(int to, int rotation) {
- SeekBar seekbar = mChild.findViewById(R.id.volume_row_slider);
+ private void rotateSeekBars(View row, int to, int rotation) {
+ SeekBar seekbar = row.findViewById(R.id.volume_row_slider);
if (seekbar != null) {
seekbar.setRotation((float) rotation);
}
- View parent = mChild.findViewById(R.id.volume_row_slider_frame);
+ View parent = row.findViewById(R.id.volume_row_slider_frame);
swapDimens(parent);
ViewGroup.LayoutParams params = seekbar.getLayoutParams();
ViewGroup.LayoutParams parentParams = parent.getLayoutParams();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java
new file mode 100644
index 000000000000..8e0426a15eee
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.chooser;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.os.Binder;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import com.android.systemui.chooser.ChooserHelper;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyFloat;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ChooserHelperTest extends SysuiTestCase {
+
+ @Test
+ public void testOnChoose_CallsStartActivityAsCallerWithToken() {
+ final Intent intent = new Intent();
+ final Binder token = new Binder();
+ intent.putExtra(ActivityManager.EXTRA_PERMISSION_TOKEN, token);
+
+ final Activity mockActivity = mock(Activity.class);
+ when(mockActivity.getIntent()).thenReturn(intent);
+
+ ChooserHelper.onChoose(mockActivity);
+ verify(mockActivity, times(1)).startActivityAsCaller(
+ any(), any(), eq(token), anyBoolean(), anyInt());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java
new file mode 100644
index 000000000000..76a3c95cad0a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/** A simple receiver to wait for broadcast intents in tests. */
+public class BlockingQueueIntentReceiver extends BroadcastReceiver {
+ private final BlockingQueue<Intent> mQueue = new ArrayBlockingQueue<Intent>(1);
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mQueue.add(intent);
+ }
+
+ public Intent waitForIntent() throws InterruptedException {
+ return mQueue.poll(10, TimeUnit.SECONDS);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
new file mode 100644
index 000000000000..63920a4f36e2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ShortcutManager;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.widget.EditText;
+import android.widget.ImageButton;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationTestHelper;
+import com.android.systemui.statusbar.RemoteInputController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class RemoteInputViewTest extends SysuiTestCase {
+
+ private static final String TEST_RESULT_KEY = "test_result_key";
+ private static final String TEST_REPLY = "hello";
+ private static final String TEST_ACTION = "com.android.ACTION";
+
+ @Mock private RemoteInputController mController;
+ @Mock private ShortcutManager mShortcutManager;
+ private BlockingQueueIntentReceiver mReceiver;
+ private RemoteInputView mView;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mReceiver = new BlockingQueueIntentReceiver();
+ mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION));
+
+ // Avoid SecurityException RemoteInputView#sendRemoteInput().
+ mContext.addMockSystemService(ShortcutManager.class, mShortcutManager);
+
+ ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow();
+ mView = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
+ }
+
+ @Test
+ public void testSendRemoteInput_intentContainsResultsAndSource() throws InterruptedException {
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
+ new Intent(TEST_ACTION), 0);
+ RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).build();
+
+ mView.setPendingIntent(pendingIntent);
+ mView.setRemoteInput(new RemoteInput[]{input}, input);
+ mView.focus();
+
+ EditText editText = mView.findViewById(R.id.remote_input_text);
+ editText.setText(TEST_REPLY);
+ ImageButton sendButton = mView.findViewById(R.id.remote_input_send);
+ sendButton.performClick();
+
+ Intent resultIntent = mReceiver.waitForIntent();
+ assertEquals(TEST_REPLY,
+ RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY));
+ assertEquals(RemoteInput.SOURCE_FREE_FORM_INPUT,
+ RemoteInput.getResultsSource(resultIntent));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
new file mode 100644
index 000000000000..0c3637d6e234
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class SmartReplyViewTest extends SysuiTestCase {
+
+ private static final String TEST_RESULT_KEY = "test_result_key";
+ private static final String TEST_ACTION = "com.android.ACTION";
+ private static final String[] TEST_CHOICES = new String[]{"Hello", "What's up?", "I'm here"};
+
+ private BlockingQueueIntentReceiver mReceiver;
+ private SmartReplyView mView;
+
+ @Before
+ public void setUp() {
+ mReceiver = new BlockingQueueIntentReceiver();
+ mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION));
+
+ mView = SmartReplyView.inflate(mContext, null);
+ }
+
+ @Test
+ public void testSendSmartReply_intentContainsResultsAndSource() throws InterruptedException {
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
+ new Intent(TEST_ACTION), 0);
+ RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).setChoices(
+ TEST_CHOICES).build();
+
+ mView.setRepliesFromRemoteInput(input, pendingIntent);
+
+ mView.getChildAt(2).performClick();
+
+ Intent resultIntent = mReceiver.waitForIntent();
+ assertEquals(TEST_CHOICES[2],
+ RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY));
+ assertEquals(RemoteInput.SOURCE_CHOICE, RemoteInput.getResultsSource(resultIntent));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
index 537d606365c4..de99d71351c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
@@ -38,6 +38,7 @@ import android.widget.TextView;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.statusbar.policy.BluetoothController;
import org.junit.After;
@@ -54,6 +55,8 @@ public class OutputChooserDialogTest extends SysuiTestCase {
OutputChooserDialog mDialog;
@Mock
+ private VolumeDialogController mVolumeController;
+ @Mock
private BluetoothController mController;
@Mock
private WifiManager mWifiManager;
@@ -69,6 +72,7 @@ public class OutputChooserDialogTest extends SysuiTestCase {
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
+ mVolumeController = mDependency.injectMockDependency(VolumeDialogController.class);
mController = mDependency.injectMockDependency(BluetoothController.class);
when(mWifiManager.isWifiEnabled()).thenReturn(true);
@@ -78,20 +82,10 @@ public class OutputChooserDialogTest extends SysuiTestCase {
mDialog = new OutputChooserDialog(getContext(), mRouter);
}
- @After
- @UiThreadTest
- public void tearDown() throws Exception {
- mDialog.dismiss();
- }
-
- private void showDialog() {
- mDialog.show();
- }
-
@Test
@UiThreadTest
public void testClickMediaRouterItemConnectsMedia() {
- showDialog();
+ mDialog.show();
OutputChooserLayout.Item item = new OutputChooserLayout.Item();
item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER;
@@ -102,12 +96,13 @@ public class OutputChooserDialogTest extends SysuiTestCase {
mDialog.onDetailItemClick(item);
verify(info, times(1)).select();
verify(mController, never()).connect(any());
+ mDialog.dismiss();
}
@Test
@UiThreadTest
public void testClickBtItemConnectsBt() {
- showDialog();
+ mDialog.show();
OutputChooserLayout.Item item = new OutputChooserLayout.Item();
item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_BT;
@@ -117,25 +112,28 @@ public class OutputChooserDialogTest extends SysuiTestCase {
mDialog.onDetailItemClick(item);
verify(mController, times(1)).connect(any());
+ mDialog.dismiss();
}
@Test
@UiThreadTest
public void testTitleNotInCall() {
- showDialog();
+ mDialog.show();
assertTrue(((TextView) mDialog.findViewById(R.id.title))
.getText().toString().contains("Media"));
+ mDialog.dismiss();
}
@Test
@UiThreadTest
public void testTitleInCall() {
mDialog.setIsInCall(true);
- showDialog();
+ mDialog.show();
assertTrue(((TextView) mDialog.findViewById(R.id.title))
.getText().toString().contains("Phone"));
+ mDialog.dismiss();
}
@Test
@@ -155,4 +153,26 @@ public class OutputChooserDialogTest extends SysuiTestCase {
verify(mRouter, times(1)).addCallback(any(), any(), anyInt());
}
+
+ @Test
+ @UiThreadTest
+ public void testRegisterCallbacks() {
+ mDialog.setIsInCall(false);
+ mDialog.onAttachedToWindow();
+
+ verify(mRouter, times(1)).addCallback(any(), any(), anyInt());
+ verify(mController, times(1)).addCallback(any());
+ verify(mVolumeController, times(1)).addCallback(any(), any());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testUnregisterCallbacks() {
+ mDialog.setIsInCall(false);
+ mDialog.onDetachedFromWindow();
+
+ verify(mRouter, times(1)).removeCallback(any());
+ verify(mController, times(1)).removeCallback(any());
+ verify(mVolumeController, times(1)).removeCallback(any());
+ }
}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 4144bbd46fb1..bfec88c0119b 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4685,7 +4685,8 @@ message MetricsEvent {
// OS: O MR
AUTOFILL_SERVICE_DISABLED_SELF = 1135;
- // Counter showing how long it took (in ms) to show the autofill UI after a field was focused
+ // Reports how long it took to show the autofill UI after a field was focused
+ // Tag FIELD_AUTOFILL_DURATION: Duration in ms
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
// Package: Package of the autofill service
// OS: O MR
@@ -4724,6 +4725,9 @@ message MetricsEvent {
// logged when we cancel an app transition.
APP_TRANSITION_CANCELLED = 1144;
+ // Tag of a field representing a duration on autofill-related metrics.
+ FIELD_AUTOFILL_DURATION = 1145;
+
// ---- End O-MR1 Constants, all O-MR1 constants go above this line ----
// OPEN: Settings > Network & Internet > Mobile network
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index e1fb3a7ca121..6b44fa5e37fb 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1860,7 +1860,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mUiLatencyHistory.log(historyLog.toString());
final LogMaker metricsLog = newLogMaker(MetricsEvent.AUTOFILL_UI_LATENCY)
- .setCounterValue((int) duration);
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, duration);
mMetricsLogger.write(metricsLog);
}
}
diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
index a4edf4e8aee8..99ffa12e3e5f 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
@@ -930,6 +930,7 @@ public class PerformBackupTask implements BackupRestoreTask {
TransportUtils.checkTransportNotNull(transport);
size = mBackupDataName.length();
if (size > 0) {
+ boolean isNonIncremental = mSavedStateName.length() == 0;
if (mStatus == BackupTransport.TRANSPORT_OK) {
backupData = ParcelFileDescriptor.open(mBackupDataName,
ParcelFileDescriptor.MODE_READ_ONLY);
@@ -938,7 +939,7 @@ public class PerformBackupTask implements BackupRestoreTask {
int userInitiatedFlag =
mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
int incrementalFlag =
- mSavedStateName.length() == 0
+ isNonIncremental
? BackupTransport.FLAG_NON_INCREMENTAL
: BackupTransport.FLAG_INCREMENTAL;
int flags = userInitiatedFlag | incrementalFlag;
@@ -946,6 +947,19 @@ public class PerformBackupTask implements BackupRestoreTask {
mStatus = transport.performBackup(mCurrentPackage, backupData, flags);
}
+ if (isNonIncremental
+ && mStatus == BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED) {
+ // TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED is only valid if the backup was
+ // incremental, as if the backup is non-incremental there is no state to
+ // clear. This avoids us ending up in a retry loop if the transport always
+ // returns this code.
+ Slog.w(TAG,
+ "Transport requested non-incremental but already the case, error");
+ backupManagerService.addBackupTrace(
+ "Transport requested non-incremental but already the case, error");
+ mStatus = BackupTransport.TRANSPORT_ERROR;
+ }
+
// TODO - We call finishBackup() for each application backed up, because
// we need to know now whether it succeeded or failed. Instead, we should
// hold off on finishBackup() until the end, which implies holding off on
@@ -993,6 +1007,31 @@ public class PerformBackupTask implements BackupRestoreTask {
BackupObserverUtils.sendBackupOnPackageResult(mObserver, pkgName,
BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED);
EventLog.writeEvent(EventLogTags.BACKUP_QUOTA_EXCEEDED, pkgName);
+
+ } else if (mStatus == BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED) {
+ Slog.i(TAG, "Transport lost data, retrying package");
+ backupManagerService.addBackupTrace(
+ "Transport lost data, retrying package:" + pkgName);
+ BackupManagerMonitorUtils.monitorEvent(
+ mMonitor,
+ BackupManagerMonitor
+ .LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT,
+ /*extras=*/ null);
+
+ mBackupDataName.delete();
+ mSavedStateName.delete();
+ mNewStateName.delete();
+
+ // Immediately retry the package by adding it back to the front of the queue.
+ // We cannot add @pm@ to the queue because we back it up separately at the start
+ // of the backup pass in state BACKUP_PM. Instead we retry this state (see
+ // below).
+ if (!PACKAGE_MANAGER_SENTINEL.equals(pkgName)) {
+ mQueue.add(0, new BackupRequest(pkgName));
+ }
+
} else {
// Actual transport-level failure to communicate the data to the backend
BackupObserverUtils.sendBackupOnPackageResult(mObserver, pkgName,
@@ -1018,6 +1057,17 @@ public class PerformBackupTask implements BackupRestoreTask {
// Success or single-package rejection. Proceed with the next app if any,
// otherwise we're done.
nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
+
+ } else if (mStatus == BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED) {
+ // We want to immediately retry the current package.
+ if (PACKAGE_MANAGER_SENTINEL.equals(pkgName)) {
+ nextState = BackupState.BACKUP_PM;
+ } else {
+ // This is an ordinary package so we will have added it back into the queue
+ // above. Thus, we proceed processing the queue.
+ nextState = BackupState.RUNNING_QUEUE;
+ }
+
} else if (mStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
if (MORE_DEBUG) {
Slog.d(TAG, "Package " + mCurrentPackage.packageName +
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 2f7d4c1ec634..266abf8c3f4c 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1042,20 +1042,14 @@ public final class ActiveServices {
throw new SecurityException("Instant app " + r.appInfo.packageName
+ " does not have permission to create foreground services");
default:
- try {
- if (AppGlobals.getPackageManager().checkPermission(
- android.Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
- r.appInfo.packageName, UserHandle.getUserId(r.appInfo.uid))
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Instant app " + r.appInfo.packageName
- + " does not have permission to create foreground"
- + "services");
- }
- } catch (RemoteException e) {
- throw new SecurityException("Failed to check instant app permission." ,
- e);
- }
- }
+ mAm.enforcePermission(
+ android.Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
+ r.app.pid, r.appInfo.uid, "startForeground");
+ }
+ } else if (r.appInfo.targetSdkVersion >= Build.VERSION_CODES.P) {
+ mAm.enforcePermission(
+ android.Manifest.permission.FOREGROUND_SERVICE,
+ r.app.pid, r.appInfo.uid, "startForeground");
}
if (r.fgRequired) {
if (DEBUG_SERVICE || DEBUG_BACKGROUND_CHECK) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5eb5b1406231..83976154ab11 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -26,6 +26,7 @@ import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.Manifest.permission.REMOVE_TASKS;
+import static android.Manifest.permission.START_ACTIVITY_AS_CALLER;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
@@ -564,6 +565,23 @@ public class ActivityManagerService extends IActivityManager.Stub
// could take much longer than usual.
static final int PROC_START_TIMEOUT_WITH_WRAPPER = 1200*1000;
+ // Permission tokens are used to temporarily granted a trusted app the ability to call
+ // #startActivityAsCaller. A client is expected to dump its token after this time has elapsed,
+ // showing any appropriate error messages to the user.
+ private static final long START_AS_CALLER_TOKEN_TIMEOUT =
+ 10 * DateUtils.MINUTE_IN_MILLIS;
+
+ // How long before the service actually expires a token. This is slightly longer than
+ // START_AS_CALLER_TOKEN_TIMEOUT, to provide a buffer so clients will rarely encounter the
+ // expiration exception.
+ private static final long START_AS_CALLER_TOKEN_TIMEOUT_IMPL =
+ START_AS_CALLER_TOKEN_TIMEOUT + 2*1000;
+
+ // How long the service will remember expired tokens, for the purpose of providing error
+ // messaging when a client uses an expired token.
+ private static final long START_AS_CALLER_TOKEN_EXPIRED_TIMEOUT =
+ START_AS_CALLER_TOKEN_TIMEOUT_IMPL + 20 * DateUtils.MINUTE_IN_MILLIS;
+
// How long we allow a receiver to run before giving up on it.
static final int BROADCAST_FG_TIMEOUT = 10*1000;
static final int BROADCAST_BG_TIMEOUT = 60*1000;
@@ -672,6 +690,13 @@ public class ActivityManagerService extends IActivityManager.Stub
final ArrayList<ActiveInstrumentation> mActiveInstrumentation = new ArrayList<>();
+ // Activity tokens of system activities that are delegating their call to
+ // #startActivityByCaller, keyed by the permissionToken granted to the delegate.
+ final HashMap<IBinder, IBinder> mStartActivitySources = new HashMap<>();
+
+ // Permission tokens that have expired, but we remember for error reporting.
+ final ArrayList<IBinder> mExpiredStartAsCallerTokens = new ArrayList<>();
+
public final IntentFirewall mIntentFirewall;
// Whether we should show our dialogs (ANR, crash, etc) or just perform their
@@ -1842,6 +1867,8 @@ public class ActivityManagerService extends IActivityManager.Stub
static final int PUSH_TEMP_WHITELIST_UI_MSG = 68;
static final int SERVICE_FOREGROUND_CRASH_MSG = 69;
static final int DISPATCH_OOM_ADJ_OBSERVER_MSG = 70;
+ static final int EXPIRE_START_AS_CALLER_TOKEN_MSG = 75;
+ static final int FORGET_START_AS_CALLER_TOKEN_MSG = 76;
static final int FIRST_ACTIVITY_STACK_MSG = 100;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -2506,6 +2533,19 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
} break;
+ case EXPIRE_START_AS_CALLER_TOKEN_MSG: {
+ synchronized (ActivityManagerService.this) {
+ final IBinder permissionToken = (IBinder)msg.obj;
+ mStartActivitySources.remove(permissionToken);
+ mExpiredStartAsCallerTokens.add(permissionToken);
+ }
+ } break;
+ case FORGET_START_AS_CALLER_TOKEN_MSG: {
+ synchronized (ActivityManagerService.this) {
+ final IBinder permissionToken = (IBinder)msg.obj;
+ mExpiredStartAsCallerTokens.remove(permissionToken);
+ }
+ } break;
}
}
};
@@ -4774,16 +4814,54 @@ public class ActivityManagerService extends IActivityManager.Stub
}
+ /**
+ * Only callable from the system. This token grants a temporary permission to call
+ * #startActivityAsCallerWithToken. The token will time out after
+ * START_AS_CALLER_TOKEN_TIMEOUT if it is not used.
+ *
+ * @param delegatorToken The Binder token referencing the system Activity that wants to delegate
+ * the #startActivityAsCaller to another app. The "caller" will be the caller of this
+ * activity's token, not the delegate's caller (which is probably the delegator itself).
+ *
+ * @return Returns a token that can be given to a "delegate" app that may call
+ * #startActivityAsCaller
+ */
@Override
- public final int startActivityAsCaller(IApplicationThread caller, String callingPackage,
- Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
- int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, boolean ignoreTargetSecurity,
- int userId) {
+ public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) {
+ int callingUid = Binder.getCallingUid();
+ if (UserHandle.getAppId(callingUid) != SYSTEM_UID) {
+ throw new SecurityException("Only the system process can request a permission token, " +
+ "received request from uid: " + callingUid);
+ }
+ IBinder permissionToken = new Binder();
+ synchronized (this) {
+ mStartActivitySources.put(permissionToken, delegatorToken);
+ }
+
+ Message expireMsg = mHandler.obtainMessage(EXPIRE_START_AS_CALLER_TOKEN_MSG,
+ permissionToken);
+ mHandler.sendMessageDelayed(expireMsg, START_AS_CALLER_TOKEN_TIMEOUT_IMPL);
+
+ Message forgetMsg = mHandler.obtainMessage(FORGET_START_AS_CALLER_TOKEN_MSG,
+ permissionToken);
+ mHandler.sendMessageDelayed(forgetMsg, START_AS_CALLER_TOKEN_EXPIRED_TIMEOUT);
+ return permissionToken;
+ }
+
+ @Override
+ public final int startActivityAsCaller(IApplicationThread caller,
+ String callingPackage, Intent intent, String resolvedType, IBinder resultTo,
+ String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo,
+ Bundle bOptions, IBinder permissionToken, boolean ignoreTargetSecurity, int userId) {
// This is very dangerous -- it allows you to perform a start activity (including
- // permission grants) as any app that may launch one of your own activities. So
- // we will only allow this to be done from activities that are part of the core framework,
- // and then only when they are running as the system.
+ // permission grants) as any app that may launch one of your own activities. So we only
+ // allow this in two cases:
+ // 1) The caller is an activity that is part of the core framework, and then only when it
+ // is running as the system.
+ // 2) The caller provides a valid permissionToken. Permission tokens are one-time use and
+ // can only be requested by a system activity, which may then delegate this call to
+ // another app.
final ActivityRecord sourceRecord;
final int targetUid;
final String targetPackage;
@@ -4791,17 +4869,47 @@ public class ActivityManagerService extends IActivityManager.Stub
if (resultTo == null) {
throw new SecurityException("Must be called from an activity");
}
- sourceRecord = mStackSupervisor.isInAnyStackLocked(resultTo);
- if (sourceRecord == null) {
- throw new SecurityException("Called with bad activity token: " + resultTo);
+
+ final IBinder sourceToken;
+ if (permissionToken != null) {
+ // To even attempt to use a permissionToken, an app must also have this signature
+ // permission.
+ enforceCallingPermission(android.Manifest.permission.START_ACTIVITY_AS_CALLER,
+ "startActivityAsCaller");
+ // If called with a permissionToken, we want the sourceRecord from the delegator
+ // activity that requested this token.
+ sourceToken =
+ mStartActivitySources.remove(permissionToken);
+ if (sourceToken == null) {
+ // Invalid permissionToken, check if it recently expired.
+ if (mExpiredStartAsCallerTokens.contains(permissionToken)) {
+ throw new SecurityException("Called with expired permission token: "
+ + permissionToken);
+ } else {
+ throw new SecurityException("Called with invalid permission token: "
+ + permissionToken);
+ }
+ }
+ } else {
+ // This method was called directly by the source.
+ sourceToken = resultTo;
}
- if (!sourceRecord.info.packageName.equals("android")) {
- throw new SecurityException(
- "Must be called from an activity that is declared in the android package");
+
+ sourceRecord = mStackSupervisor.isInAnyStackLocked(sourceToken);
+ if (sourceRecord == null) {
+ throw new SecurityException("Called with bad activity token: " + sourceToken);
}
if (sourceRecord.app == null) {
throw new SecurityException("Called without a process attached to activity");
}
+
+ // Whether called directly or from a delegate, the source activity must be from the
+ // android package.
+ if (!sourceRecord.info.packageName.equals("android")) {
+ throw new SecurityException("Must be called from an activity that is " +
+ "declared in the android package");
+ }
+
if (UserHandle.getAppId(sourceRecord.app.uid) != SYSTEM_UID) {
// This is still okay, as long as this activity is running under the
// uid of the original calling activity.
@@ -4812,6 +4920,7 @@ public class ActivityManagerService extends IActivityManager.Stub
+ sourceRecord.launchedFromUid);
}
}
+
if (ignoreTargetSecurity) {
if (intent.getComponent() == null) {
throw new SecurityException(
@@ -8775,6 +8884,20 @@ public class ActivityManagerService extends IActivityManager.Stub
/**
* This can be called with or without the global lock held.
*/
+ void enforcePermission(String permission, int pid, int uid, String func) {
+ if (checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+
+ String msg = "Permission Denial: " + func + " from pid=" + pid + ", uid=" + uid
+ + " requires " + permission;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+
+ /**
+ * This can be called with or without the global lock held.
+ */
void enforceCallerIsRecentsOrHasPermission(String permission, String func) {
if (!mRecentTasks.isCallerRecents(Binder.getCallingUid())) {
enforceCallingPermission(permission, func);
diff --git a/services/core/java/com/android/server/am/ActivityStartController.java b/services/core/java/com/android/server/am/ActivityStartController.java
index f9932b20fb5b..5551914f7063 100644
--- a/services/core/java/com/android/server/am/ActivityStartController.java
+++ b/services/core/java/com/android/server/am/ActivityStartController.java
@@ -220,7 +220,7 @@ public class ActivityStartController {
}
}
- final int startActivityInPackage(int uid, int realCallingUid, int realCallingPid,
+ final int startActivityInPackage(int uid, int realCallingPid, int realCallingUid,
String callingPackage, Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags, SafeActivityOptions options,
int userId, TaskRecord inTask, String reason) {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 8595aa394800..4dc30ddf4b5b 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1252,7 +1252,7 @@ class ActivityStarter {
outActivity[0] = reusedActivity;
}
- return START_DELIVERED_TO_TOP;
+ return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
}
}
diff --git a/services/core/java/com/android/server/am/AppErrorDialog.java b/services/core/java/com/android/server/am/AppErrorDialog.java
index 541226682bac..68c63a2d595b 100644
--- a/services/core/java/com/android/server/am/AppErrorDialog.java
+++ b/services/core/java/com/android/server/am/AppErrorDialog.java
@@ -38,9 +38,7 @@ final class AppErrorDialog extends BaseErrorDialog implements View.OnClickListen
private final ActivityManagerService mService;
private final AppErrorResult mResult;
private final ProcessRecord mProc;
- private final boolean mRepeating;
private final boolean mIsRestartable;
- private CharSequence mName;
static int CANT_SHOW = -1;
static int BACKGROUND_USER = -2;
@@ -53,6 +51,7 @@ final class AppErrorDialog extends BaseErrorDialog implements View.OnClickListen
static final int MUTE = 5;
static final int TIMEOUT = 6;
static final int CANCEL = 7;
+ static final int APP_INFO = 8;
// 5-minute timeout, then we automatically dismiss the crash dialog
static final long DISMISS_TIMEOUT = 1000 * 60 * 5;
@@ -64,23 +63,25 @@ final class AppErrorDialog extends BaseErrorDialog implements View.OnClickListen
mService = service;
mProc = data.proc;
mResult = data.result;
- mRepeating = data.repeating;
- mIsRestartable = data.task != null || data.isRestartableForService;
+ mIsRestartable = (data.task != null || data.isRestartableForService)
+ && Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.SHOW_RESTART_IN_CRASH_DIALOG, 0) != 0;
BidiFormatter bidi = BidiFormatter.getInstance();
+ CharSequence name;
if ((mProc.pkgList.size() == 1) &&
- (mName = context.getPackageManager().getApplicationLabel(mProc.info)) != null) {
+ (name = context.getPackageManager().getApplicationLabel(mProc.info)) != null) {
setTitle(res.getString(
- mRepeating ? com.android.internal.R.string.aerr_application_repeated
+ data.repeating ? com.android.internal.R.string.aerr_application_repeated
: com.android.internal.R.string.aerr_application,
- bidi.unicodeWrap(mName.toString()),
+ bidi.unicodeWrap(name.toString()),
bidi.unicodeWrap(mProc.info.processName)));
} else {
- mName = mProc.processName;
+ name = mProc.processName;
setTitle(res.getString(
- mRepeating ? com.android.internal.R.string.aerr_process_repeated
+ data.repeating ? com.android.internal.R.string.aerr_process_repeated
: com.android.internal.R.string.aerr_process,
- bidi.unicodeWrap(mName.toString())));
+ bidi.unicodeWrap(name.toString())));
}
setCancelable(true);
@@ -118,11 +119,14 @@ final class AppErrorDialog extends BaseErrorDialog implements View.OnClickListen
report.setOnClickListener(this);
report.setVisibility(hasReceiver ? View.VISIBLE : View.GONE);
final TextView close = findViewById(com.android.internal.R.id.aerr_close);
- close.setVisibility(mRepeating ? View.VISIBLE : View.GONE);
close.setOnClickListener(this);
+ final TextView appInfo = findViewById(com.android.internal.R.id.aerr_app_info);
+ appInfo.setOnClickListener(this);
boolean showMute = !Build.IS_USER && Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0
+ && Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.SHOW_MUTE_IN_CRASH_DIALOG, 0) != 0;
final TextView mute = findViewById(com.android.internal.R.id.aerr_mute);
mute.setOnClickListener(this);
mute.setVisibility(showMute ? View.VISIBLE : View.GONE);
@@ -183,6 +187,9 @@ final class AppErrorDialog extends BaseErrorDialog implements View.OnClickListen
case com.android.internal.R.id.aerr_close:
mHandler.obtainMessage(FORCE_QUIT).sendToTarget();
break;
+ case com.android.internal.R.id.aerr_app_info:
+ mHandler.obtainMessage(APP_INFO).sendToTarget();
+ break;
case com.android.internal.R.id.aerr_mute:
mHandler.obtainMessage(MUTE).sendToTarget();
break;
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index c7d93be893fc..9776c4d2f947 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -34,6 +34,7 @@ import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
+import android.net.Uri;
import android.os.Binder;
import android.os.Message;
import android.os.Process;
@@ -500,6 +501,11 @@ class AppErrors {
Binder.restoreCallingIdentity(orig);
}
}
+ if (res == AppErrorDialog.APP_INFO) {
+ appErrorIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ appErrorIntent.setData(Uri.parse("package:" + r.info.packageName));
+ appErrorIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
if (res == AppErrorDialog.FORCE_QUIT_AND_REPORT) {
appErrorIntent = createAppErrorIntentLocked(r, timeMillis, crashInfo);
}
diff --git a/services/core/java/com/android/server/media/MediaUpdateService.java b/services/core/java/com/android/server/media/MediaUpdateService.java
index 016d062db726..6921ccde250c 100644
--- a/services/core/java/com/android/server/media/MediaUpdateService.java
+++ b/services/core/java/com/android/server/media/MediaUpdateService.java
@@ -53,13 +53,10 @@ public class MediaUpdateService extends SystemService {
@Override
public void onStart() {
- // TODO: Uncomment below once sepolicy change is landed.
- /*
if ("userdebug".equals(android.os.Build.TYPE) || "eng".equals(android.os.Build.TYPE)) {
connect();
registerBroadcastReceiver();
}
- */
}
private void connect() {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index efe54faa0f79..4c9da8949cc9 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
+import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -1881,6 +1882,18 @@ public class NotificationManagerService extends SystemService {
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, true,
UserHandle.getUserId(uid), REASON_PACKAGE_BANNED, null);
}
+
+ try {
+ getContext().sendBroadcastAsUser(
+ new Intent(ACTION_APP_BLOCK_STATE_CHANGED)
+ .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, !enabled)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .setPackage(pkg),
+ UserHandle.of(UserHandle.getUserId(uid)), null);
+ } catch (SecurityException e) {
+ Slog.w(TAG, "Can't notify app about app block change", e);
+ }
+
savePolicyFile();
}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 2a153ecbd776..a53627093df6 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -19,15 +19,6 @@ package com.android.server.power;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
-
-import com.android.internal.app.IAppOpsService;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.server.EventLogTags;
-import com.android.server.LocalServices;
-import com.android.server.policy.WindowManagerPolicy;
-
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -54,6 +45,15 @@ import android.util.EventLog;
import android.util.Slog;
import android.view.inputmethod.InputMethodManagerInternal;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.server.EventLogTags;
+import com.android.server.LocalServices;
+import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.policy.WindowManagerPolicy;
+
/**
* Sends broadcasts about important power state changes.
* <p>
@@ -96,6 +96,7 @@ final class Notifier {
private final ActivityManagerInternal mActivityManagerInternal;
private final InputManagerInternal mInputManagerInternal;
private final InputMethodManagerInternal mInputMethodManagerInternal;
+ private final StatusBarManagerInternal mStatusBarManagerInternal;
private final TrustManager mTrustManager;
private final NotifierHandler mHandler;
@@ -142,6 +143,7 @@ final class Notifier {
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
+ mStatusBarManagerInternal = LocalServices.getService(StatusBarManagerInternal.class);
mTrustManager = mContext.getSystemService(TrustManager.class);
mHandler = new NotifierHandler(looper);
@@ -545,26 +547,27 @@ final class Notifier {
}
/**
- * Called when wireless charging has started so as to provide user feedback.
+ * Called when profile screen lock timeout has expired.
*/
- public void onWirelessChargingStarted() {
- if (DEBUG) {
- Slog.d(TAG, "onWirelessChargingStarted");
- }
-
- mSuspendBlocker.acquire();
- Message msg = mHandler.obtainMessage(MSG_WIRELESS_CHARGING_STARTED);
+ public void onProfileTimeout(@UserIdInt int userId) {
+ final Message msg = mHandler.obtainMessage(MSG_PROFILE_TIMED_OUT);
msg.setAsynchronous(true);
+ msg.arg1 = userId;
mHandler.sendMessage(msg);
}
/**
- * Called when profile screen lock timeout has expired.
+ * Called when wireless charging has started so as to provide user feedback (sound and visual).
*/
- public void onProfileTimeout(@UserIdInt int userId) {
- final Message msg = mHandler.obtainMessage(MSG_PROFILE_TIMED_OUT);
+ public void onWirelessChargingStarted(int batteryLevel) {
+ if (DEBUG) {
+ Slog.d(TAG, "onWirelessChargingStarted");
+ }
+
+ mSuspendBlocker.acquire();
+ Message msg = mHandler.obtainMessage(MSG_WIRELESS_CHARGING_STARTED);
msg.setAsynchronous(true);
- msg.arg1 = userId;
+ msg.arg1 = batteryLevel;
mHandler.sendMessage(msg);
}
@@ -715,7 +718,11 @@ final class Notifier {
}
}
}
+ }
+ private void showWirelessChargingStarted(int batteryLevel) {
+ playWirelessChargingStartedSound();
+ mStatusBarManagerInternal.showChargingAnimation(batteryLevel);
mSuspendBlocker.release();
}
@@ -738,7 +745,7 @@ final class Notifier {
sendNextBroadcast();
break;
case MSG_WIRELESS_CHARGING_STARTED:
- playWirelessChargingStartedSound();
+ showWirelessChargingStarted(msg.arg1);
break;
case MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED:
sendBrightnessBoostChangedBroadcast();
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 7273f62e3d39..fbdedceabb3b 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -16,6 +16,11 @@
package com.android.server.power;
+import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
+import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
+import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
+import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
+
import android.annotation.IntDef;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -103,11 +108,6 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
-import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
-import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
-import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
-import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
-
/**
* The power manager service is responsible for coordinating power management
* functions on the device.
@@ -119,6 +119,9 @@ public final class PowerManagerService extends SystemService
private static final boolean DEBUG = false;
private static final boolean DEBUG_SPEW = DEBUG && true;
+ // if DEBUG_WIRELESS=true, plays wireless charging animation w/ sound on every plug + unplug
+ private static final boolean DEBUG_WIRELESS = false;
+
// Message: Sent when a user activity timeout occurs to update the power state.
private static final int MSG_USER_ACTIVITY_TIMEOUT = 1;
// Message: Sent when the device enters or exits a dreaming or dozing state.
@@ -1794,8 +1797,8 @@ public final class PowerManagerService extends SystemService
// Tell the notifier whether wireless charging has started so that
// it can provide feedback to the user.
- if (dockedOnWirelessCharger) {
- mNotifier.onWirelessChargingStarted();
+ if (dockedOnWirelessCharger || DEBUG_WIRELESS) {
+ mNotifier.onWirelessChargingStarted(mBatteryLevel);
}
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 95006ffb2ed6..3ab771b7c6ec 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -37,6 +37,8 @@ public interface StatusBarManagerInternal {
void dismissKeyboardShortcutsMenu();
void toggleKeyboardShortcutsMenu(int deviceId);
+ void showChargingAnimation(int batteryLevel);
+
/**
* Show picture-in-picture menu.
*/
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index d67f2d7922c0..adb368b074c0 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -319,6 +319,16 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
}
@Override
+ public void showChargingAnimation(int batteryLevel) {
+ if (mBar != null) {
+ try {
+ mBar.showChargingAnimation(batteryLevel);
+ } catch (RemoteException ex){
+ }
+ }
+ }
+
+ @Override
public void showPictureInPictureMenu() {
if (mBar != null) {
try {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index cb346cc877ae..38e2168073ea 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7902,6 +7902,37 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
enforceSystemUserOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
}
+ private boolean canUserUseLockTaskLocked(int userId) {
+ if (isUserAffiliatedWithDeviceLocked(userId)) {
+ return true;
+ }
+
+ // Unaffiliated profile owners are not allowed to use lock when there is a device owner.
+ if (mOwners.hasDeviceOwner()) {
+ return false;
+ }
+
+ final ComponentName profileOwner = getProfileOwner(userId);
+ if (profileOwner == null) {
+ return false;
+ }
+
+ // Managed profiles are not allowed to use lock task
+ if (isManagedProfile(userId)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void enforceCanCallLockTaskLocked(ComponentName who) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ final int userId = mInjector.userHandleGetCallingUserId();
+ if (!canUserUseLockTaskLocked(userId)) {
+ throw new SecurityException("User " + userId + " is not allowed to use lock task");
+ }
+ }
+
private void ensureCallerPackage(@Nullable String packageName) {
if (packageName == null) {
Preconditions.checkState(isCallerWithSystemUid(),
@@ -9608,14 +9639,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Preconditions.checkNotNull(packages, "packages is null");
synchronized (this) {
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ enforceCanCallLockTaskLocked(who);
final int userHandle = mInjector.userHandleGetCallingUserId();
- if (isUserAffiliatedWithDeviceLocked(userHandle)) {
- setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
- } else {
- throw new SecurityException("Admin " + who +
- " is neither the device owner or affiliated user's profile owner.");
- }
+ setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
}
}
@@ -9634,12 +9660,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final int userHandle = mInjector.binderGetCallingUserHandle().getIdentifier();
synchronized (this) {
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- if (!isUserAffiliatedWithDeviceLocked(userHandle)) {
- throw new SecurityException("Admin " + who +
- " is neither the device owner or affiliated user's profile owner.");
- }
-
+ enforceCanCallLockTaskLocked(who);
final List<String> packages = getUserData(userHandle).mLockTaskPackages;
return packages.toArray(new String[packages.size()]);
}
@@ -9658,11 +9679,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Preconditions.checkNotNull(who, "ComponentName is null");
final int userHandle = mInjector.userHandleGetCallingUserId();
synchronized (this) {
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- if (!isUserAffiliatedWithDeviceLocked(userHandle)) {
- throw new SecurityException("Admin " + who +
- " is neither the device owner or affiliated user's profile owner.");
- }
+ enforceCanCallLockTaskLocked(who);
setLockTaskFeaturesLocked(userHandle, flags);
}
}
@@ -9679,11 +9696,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Preconditions.checkNotNull(who, "ComponentName is null");
final int userHandle = mInjector.userHandleGetCallingUserId();
synchronized (this) {
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- if (!isUserAffiliatedWithDeviceLocked(userHandle)) {
- throw new SecurityException("Admin " + who +
- " is neither the device owner or affiliated user's profile owner.");
- }
+ enforceCanCallLockTaskLocked(who);
return getUserData(userHandle).mLockTaskFeatures;
}
}
@@ -9694,7 +9707,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true);
for (int i = userInfos.size() - 1; i >= 0; i--) {
int userId = userInfos.get(i).id;
- if (isUserAffiliatedWithDeviceLocked(userId)) {
+ if (canUserUseLockTaskLocked(userId)) {
continue;
}
@@ -11232,10 +11245,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// of a split user device.
return true;
}
+
final ComponentName profileOwner = getProfileOwner(userId);
if (profileOwner == null) {
return false;
}
+
final Set<String> userAffiliationIds = getUserData(userId).mAffiliationIds;
final Set<String> deviceAffiliationIds =
getUserData(UserHandle.USER_SYSTEM).mAffiliationIds;
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 0499bf0eccc7..94e4e306be15 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -59,6 +59,7 @@
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<!-- Uses API introduced in O (26) -->
<uses-sdk android:minSdkVersion="1"
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 5825a8b70bfb..bc65df83fe1b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3653,15 +3653,47 @@ public class DevicePolicyManagerTest extends DpmTestBase {
MoreAsserts.assertEmpty(targetUsers);
}
+ private void verifyLockTaskState(int userId) throws Exception {
+ verifyLockTaskState(userId, new String[0], DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+ }
+
+ private void verifyLockTaskState(int userId, String[] packages, int flags) throws Exception {
+ verify(getServices().iactivityManager).updateLockTaskPackages(userId, packages);
+ verify(getServices().iactivityManager).updateLockTaskFeatures(userId, flags);
+ }
+
+ private void verifyCanSetLockTask(int uid, int userId, ComponentName who, String[] packages,
+ int flags) throws Exception {
+ mContext.binder.callingUid = uid;
+ dpm.setLockTaskPackages(who, packages);
+ MoreAsserts.assertEquals(packages, dpm.getLockTaskPackages(who));
+ for (String p : packages) {
+ assertTrue(dpm.isLockTaskPermitted(p));
+ }
+ assertFalse(dpm.isLockTaskPermitted("anotherPackage"));
+ // Test to see if set lock task features can be set
+ dpm.setLockTaskFeatures(who, flags);
+ verifyLockTaskState(userId, packages, flags);
+ }
+
+ private void verifyCanNotSetLockTask(int uid, ComponentName who, String[] packages,
+ int flags) throws Exception {
+ mContext.binder.callingUid = uid;
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpm.setLockTaskPackages(who, packages));
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpm.getLockTaskPackages(who));
+ assertFalse(dpm.isLockTaskPermitted("doPackage1"));
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpm.setLockTaskFeatures(who, flags));
+ }
+
public void testLockTaskPolicyAllowedForAffiliatedUsers() throws Exception {
// Setup a device owner.
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
// Lock task policy is updated when loading user data.
- verify(getServices().iactivityManager).updateLockTaskPackages(
- UserHandle.USER_SYSTEM, new String[0]);
- verify(getServices().iactivityManager).updateLockTaskFeatures(
- UserHandle.USER_SYSTEM, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+ verifyLockTaskState(UserHandle.USER_SYSTEM);
// Set up a managed profile managed by different package (package name shouldn't matter)
final int MANAGED_PROFILE_USER_ID = 15;
@@ -3669,40 +3701,30 @@ public class DevicePolicyManagerTest extends DpmTestBase {
final ComponentName adminDifferentPackage =
new ComponentName("another.package", "whatever.class");
addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2);
- verify(getServices().iactivityManager).updateLockTaskPackages(
- MANAGED_PROFILE_USER_ID, new String[0]);
- verify(getServices().iactivityManager).updateLockTaskFeatures(
- MANAGED_PROFILE_USER_ID, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+ verifyLockTaskState(MANAGED_PROFILE_USER_ID);
+
+ // Setup a PO on the secondary user
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ setAsProfileOwner(admin3);
+ verifyLockTaskState(DpmMockContext.CALLER_USER_HANDLE);
// The DO can still set lock task packages
- mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
final String[] doPackages = {"doPackage1", "doPackage2"};
- dpm.setLockTaskPackages(admin1, doPackages);
- MoreAsserts.assertEquals(doPackages, dpm.getLockTaskPackages(admin1));
- assertTrue(dpm.isLockTaskPermitted("doPackage1"));
- assertFalse(dpm.isLockTaskPermitted("anotherPackage"));
- verify(getServices().iactivityManager).updateLockTaskPackages(
- UserHandle.USER_SYSTEM, doPackages);
- // And the DO can still set lock task features
- final int doFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
+ final int flags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
+ | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+ verifyCanSetLockTask(DpmMockContext.CALLER_SYSTEM_USER_UID, UserHandle.USER_SYSTEM, admin1, doPackages, flags);
+
+ final String[] secondaryPoPackages = {"secondaryPoPackage1", "secondaryPoPackage2"};
+ final int secondaryPoFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
| DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
- dpm.setLockTaskFeatures(admin1, doFlags);
- verify(getServices().iactivityManager).updateLockTaskFeatures(
- UserHandle.USER_SYSTEM, doFlags);
+ verifyCanNotSetLockTask(DpmMockContext.CALLER_UID, admin3, secondaryPoPackages, secondaryPoFlags);
// Managed profile is unaffiliated - shouldn't be able to setLockTaskPackages.
mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
final String[] poPackages = {"poPackage1", "poPackage2"};
- assertExpectException(SecurityException.class, /* messageRegex =*/ null,
- () -> dpm.setLockTaskPackages(adminDifferentPackage, poPackages));
- assertExpectException(SecurityException.class, /* messageRegex =*/ null,
- () -> dpm.getLockTaskPackages(adminDifferentPackage));
- assertFalse(dpm.isLockTaskPermitted("doPackage1"));
- // And it shouldn't be able to setLockTaskFeatures.
final int poFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
| DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
- assertExpectException(SecurityException.class, /* messageRegex =*/ null,
- () -> dpm.setLockTaskFeatures(adminDifferentPackage, poFlags));
+ verifyCanNotSetLockTask(MANAGED_PROFILE_ADMIN_UID, adminDifferentPackage, poPackages, poFlags);
// Setting same affiliation ids
final Set<String> userAffiliationIds = Collections.singleton("some-affiliation-id");
@@ -3717,12 +3739,9 @@ public class DevicePolicyManagerTest extends DpmTestBase {
MoreAsserts.assertEquals(poPackages, dpm.getLockTaskPackages(adminDifferentPackage));
assertTrue(dpm.isLockTaskPermitted("poPackage1"));
assertFalse(dpm.isLockTaskPermitted("doPackage2"));
- verify(getServices().iactivityManager).updateLockTaskPackages(
- MANAGED_PROFILE_USER_ID, poPackages);
// And it can set lock task features.
dpm.setLockTaskFeatures(adminDifferentPackage, poFlags);
- verify(getServices().iactivityManager).updateLockTaskFeatures(
- MANAGED_PROFILE_USER_ID, poFlags);
+ verifyLockTaskState(MANAGED_PROFILE_USER_ID, poPackages, poFlags);
// Unaffiliate the profile, lock task mode no longer available on the profile.
dpm.setAffiliationIds(adminDifferentPackage, Collections.emptySet());
@@ -3733,8 +3752,38 @@ public class DevicePolicyManagerTest extends DpmTestBase {
verify(getServices().iactivityManager, times(2)).updateLockTaskFeatures(
MANAGED_PROFILE_USER_ID, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+ // Verify that lock task packages were not cleared for the DO
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
assertTrue(dpm.isLockTaskPermitted("doPackage1"));
+
+ }
+
+ public void testLockTaskPolicyForProfileOwner() throws Exception {
+ // Setup a PO
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ setAsProfileOwner(admin1);
+ verifyLockTaskState(DpmMockContext.CALLER_USER_HANDLE);
+
+ final String[] poPackages = {"poPackage1", "poPackage2"};
+ final int poFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
+ | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+ verifyCanSetLockTask(DpmMockContext.CALLER_UID, DpmMockContext.CALLER_USER_HANDLE, admin1,
+ poPackages, poFlags);
+
+ // Set up a managed profile managed by different package (package name shouldn't matter)
+ final int MANAGED_PROFILE_USER_ID = 15;
+ final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 20456);
+ final ComponentName adminDifferentPackage =
+ new ComponentName("another.package", "whatever.class");
+ addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2);
+ verifyLockTaskState(MANAGED_PROFILE_USER_ID);
+
+ // Managed profile is unaffiliated - shouldn't be able to setLockTaskPackages.
+ mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+ final String[] mpoPackages = {"poPackage1", "poPackage2"};
+ final int mpoFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
+ | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+ verifyCanNotSetLockTask(MANAGED_PROFILE_ADMIN_UID, adminDifferentPackage, mpoPackages, mpoFlags);
}
public void testIsDeviceManaged() throws Exception {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index e0bebee2475e..9ae6f00f44ca 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -1179,6 +1179,34 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testUpdateAppNotifyCreatorBlock() throws Exception {
+ mService.setRankingHelper(mRankingHelper);
+
+ mBinderService.setNotificationsEnabledForPackage(PKG, 0, false);
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+ assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
+ captor.getValue().getAction());
+ assertEquals(PKG, captor.getValue().getPackage());
+ assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false));
+ }
+
+ @Test
+ public void testUpdateAppNotifyCreatorUnblock() throws Exception {
+ mService.setRankingHelper(mRankingHelper);
+
+ mBinderService.setNotificationsEnabledForPackage(PKG, 0, true);
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+ assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
+ captor.getValue().getAction());
+ assertEquals(PKG, captor.getValue().getPackage());
+ assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
+ }
+
+ @Test
public void testUpdateChannelNotifyCreatorBlock() throws Exception {
mService.setRankingHelper(mRankingHelper);
when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index 98ea45158ba1..0ee870aaa24b 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -251,7 +251,15 @@ public class PhoneStateListener {
*/
public static final int LISTEN_USER_MOBILE_DATA_STATE = 0x00080000;
- /*
+ /**
+ * Listen for changes to the physical channel configuration.
+ *
+ * @see #onPhysicalChannelConfigurationChanged
+ * @hide
+ */
+ public static final int LISTEN_PHYSICAL_CHANNEL_CONFIGURATION = 0x00100000;
+
+ /*
* Subscription used to listen to the phone state changes
* @hide
*/
@@ -362,7 +370,10 @@ public class PhoneStateListener {
case LISTEN_CARRIER_NETWORK_CHANGE:
PhoneStateListener.this.onCarrierNetworkChange((boolean)msg.obj);
break;
-
+ case LISTEN_PHYSICAL_CHANNEL_CONFIGURATION:
+ PhoneStateListener.this.onPhysicalChannelConfigurationChanged(
+ (List<PhysicalChannelConfig>)msg.obj);
+ break;
}
}
};
@@ -561,6 +572,16 @@ public class PhoneStateListener {
}
/**
+ * Callback invoked when the current physical channel configuration has changed
+ *
+ * @param configs List of the current {@link PhysicalChannelConfig}s
+ * @hide
+ */
+ public void onPhysicalChannelConfigurationChanged(List<PhysicalChannelConfig> configs) {
+ // default implementation empty
+ }
+
+ /**
* Callback invoked when telephony has received notice from a carrier
* app that a network action that could result in connectivity loss
* has been requested by an app using
diff --git a/telephony/java/android/telephony/PhysicalChannelConfig.aidl b/telephony/java/android/telephony/PhysicalChannelConfig.aidl
new file mode 100644
index 000000000000..651c103b439d
--- /dev/null
+++ b/telephony/java/android/telephony/PhysicalChannelConfig.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2018, 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.telephony;
+
+parcelable PhysicalChannelConfig; \ No newline at end of file
diff --git a/telephony/java/android/telephony/PhysicalChannelConfig.java b/telephony/java/android/telephony/PhysicalChannelConfig.java
new file mode 100644
index 000000000000..651d68d833dc
--- /dev/null
+++ b/telephony/java/android/telephony/PhysicalChannelConfig.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2018 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.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.annotation.IntDef;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide
+ */
+public final class PhysicalChannelConfig implements Parcelable {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({CONNECTION_PRIMARY_SERVING, CONNECTION_SECONDARY_SERVING})
+ public @interface ConnectionStatus {}
+
+ /**
+ * UE has connection to cell for signalling and possibly data (3GPP 36.331, 25.331).
+ */
+ public static final int CONNECTION_PRIMARY_SERVING = 1;
+
+ /**
+ * UE has connection to cell for data (3GPP 36.331, 25.331).
+ */
+ public static final int CONNECTION_SECONDARY_SERVING = 2;
+
+ /**
+ * Connection status of the cell.
+ *
+ * <p>One of {@link #CONNECTION_PRIMARY_SERVING}, {@link #CONNECTION_SECONDARY_SERVING}.
+ */
+ private int mCellConnectionStatus;
+
+ /**
+ * Cell bandwidth, in kHz.
+ */
+ private int mCellBandwidthDownlinkKhz;
+
+ public PhysicalChannelConfig(int status, int bandwidth) {
+ mCellConnectionStatus = status;
+ mCellBandwidthDownlinkKhz = bandwidth;
+ }
+
+ public PhysicalChannelConfig(Parcel in) {
+ mCellConnectionStatus = in.readInt();
+ mCellBandwidthDownlinkKhz = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mCellConnectionStatus);
+ dest.writeInt(mCellBandwidthDownlinkKhz);
+ }
+
+ /**
+ * @return Cell bandwidth, in kHz
+ */
+ public int getCellBandwidthDownlink() {
+ return mCellBandwidthDownlinkKhz;
+ }
+
+ /**
+ * Gets the connection status of the cell.
+ *
+ * @see #CONNECTION_PRIMARY_SERVING
+ * @see #CONNECTION_SECONDARY_SERVING
+ *
+ * @return Connection status of the cell
+ */
+ @ConnectionStatus
+ public int getConnectionStatus() {
+ return mCellConnectionStatus;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof PhysicalChannelConfig)) {
+ return false;
+ }
+
+ PhysicalChannelConfig config = (PhysicalChannelConfig) o;
+ return mCellConnectionStatus == config.mCellConnectionStatus
+ && mCellBandwidthDownlinkKhz == config.mCellBandwidthDownlinkKhz;
+ }
+
+ @Override
+ public int hashCode() {
+ return (mCellBandwidthDownlinkKhz * 29) + (mCellConnectionStatus * 31);
+ }
+
+ public static final Parcelable.Creator<PhysicalChannelConfig> CREATOR =
+ new Parcelable.Creator<PhysicalChannelConfig>() {
+ public PhysicalChannelConfig createFromParcel(Parcel in) {
+ return new PhysicalChannelConfig(in);
+ }
+
+ public PhysicalChannelConfig[] newArray(int size) {
+ return new PhysicalChannelConfig[size];
+ }
+ };
+}
diff --git a/tests/FrameworkPerf/AndroidManifest.xml b/tests/FrameworkPerf/AndroidManifest.xml
index 2591aaf8f1a6..d62ef9ec210c 100644
--- a/tests/FrameworkPerf/AndroidManifest.xml
+++ b/tests/FrameworkPerf/AndroidManifest.xml
@@ -1,5 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworkperf">
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-sdk android:minSdkVersion="5" />
diff --git a/tests/OneMedia/AndroidManifest.xml b/tests/OneMedia/AndroidManifest.xml
index c6824ecea976..8697f1b085bf 100644
--- a/tests/OneMedia/AndroidManifest.xml
+++ b/tests/OneMedia/AndroidManifest.xml
@@ -5,6 +5,7 @@
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="19"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
diff --git a/tests/notification/Android.mk b/tests/notification/Android.mk
index 0669553bf03e..255e6e70a921 100644
--- a/tests/notification/Android.mk
+++ b/tests/notification/Android.mk
@@ -7,7 +7,7 @@ LOCAL_MODULE_TAGS := tests
# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
LOCAL_PACKAGE_NAME := NotificationTests
LOCAL_SDK_VERSION := 21
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index e0e6b5883e32..3dbb50306cc6 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -317,6 +317,7 @@ write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributio
fprintf(out, "\n");
fprintf(out, "#include <stdint.h>\n");
fprintf(out, "#include <vector>\n");
+ fprintf(out, "#include <set>\n");
fprintf(out, "\n");
fprintf(out, "namespace android {\n");
@@ -361,6 +362,36 @@ write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributio
fprintf(out, "};\n");
fprintf(out, "\n");
+ fprintf(out, "const static std::set<int> kAtomsWithUidField = {\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ for (vector<AtomField>::const_iterator field = atom->fields.begin();
+ field != atom->fields.end(); field++) {
+ if (field->name == "uid") {
+ string constant = make_constant_name(atom->name);
+ fprintf(out, " %s,\n", constant.c_str());
+ break;
+ }
+ }
+ }
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+
+ fprintf(out, "const static std::set<int> kAtomsWithAttributionChain = {\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ for (vector<AtomField>::const_iterator field = atom->fields.begin();
+ field != atom->fields.end(); field++) {
+ if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ string constant = make_constant_name(atom->name);
+ fprintf(out, " %s,\n", constant.c_str());
+ break;
+ }
+ }
+ }
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+
fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n", maxPushedAtomId);
// Print write methods
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index e9e61a546e14..101b3e20690f 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -66,11 +66,11 @@ interface IWifiManager
List<OsuProvider> getMatchingOsuProviders(in ScanResult scanResult);
- int addOrUpdateNetwork(in WifiConfiguration config);
+ int addOrUpdateNetwork(in WifiConfiguration config, String packageName);
- boolean addOrUpdatePasspointConfiguration(in PasspointConfiguration config);
+ boolean addOrUpdatePasspointConfiguration(in PasspointConfiguration config, String packageName);
- boolean removePasspointConfiguration(in String fqdn);
+ boolean removePasspointConfiguration(in String fqdn, String packageName);
List<PasspointConfiguration> getPasspointConfigurations();
@@ -80,21 +80,21 @@ interface IWifiManager
void deauthenticateNetwork(long holdoff, boolean ess);
- boolean removeNetwork(int netId);
+ boolean removeNetwork(int netId, String packageName);
- boolean enableNetwork(int netId, boolean disableOthers);
+ boolean enableNetwork(int netId, boolean disableOthers, String packageName);
- boolean disableNetwork(int netId);
+ boolean disableNetwork(int netId, String packageName);
- void startScan(in ScanSettings requested, in WorkSource ws, in String packageName);
+ void startScan(in ScanSettings requested, in WorkSource ws, String packageName);
List<ScanResult> getScanResults(String callingPackage);
- void disconnect();
+ void disconnect(String packageName);
- void reconnect();
+ void reconnect(String packageName);
- void reassociate();
+ void reassociate(String packageName);
WifiInfo getConnectionInfo(String callingPackage);
@@ -108,7 +108,7 @@ interface IWifiManager
boolean isDualBandSupported();
- boolean saveConfiguration();
+ boolean saveConfiguration(String packageName);
DhcpInfo getDhcpInfo();
@@ -134,7 +134,7 @@ interface IWifiManager
boolean stopSoftAp();
- int startLocalOnlyHotspot(in Messenger messenger, in IBinder binder, in String packageName);
+ int startLocalOnlyHotspot(in Messenger messenger, in IBinder binder, String packageName);
void stopLocalOnlyHotspot();
@@ -146,9 +146,9 @@ interface IWifiManager
WifiConfiguration getWifiApConfiguration();
- void setWifiApConfiguration(in WifiConfiguration wifiConfig);
+ void setWifiApConfiguration(in WifiConfiguration wifiConfig, String packageName);
- Messenger getWifiServiceMessenger();
+ Messenger getWifiServiceMessenger(String packageName);
void enableTdls(String remoteIPAddress, boolean enable);
@@ -165,9 +165,9 @@ interface IWifiManager
void enableWifiConnectivityManager(boolean enabled);
- void disableEphemeralNetwork(String SSID);
+ void disableEphemeralNetwork(String SSID, String packageName);
- void factoryReset();
+ void factoryReset(String packageName);
Network getCurrentNetwork();
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index e4b510db68f4..50ae905990ed 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1132,7 +1132,7 @@ public class WifiManager {
*/
private int addOrUpdateNetwork(WifiConfiguration config) {
try {
- return mService.addOrUpdateNetwork(config);
+ return mService.addOrUpdateNetwork(config, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1153,7 +1153,7 @@ public class WifiManager {
*/
public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
try {
- if (!mService.addOrUpdatePasspointConfiguration(config)) {
+ if (!mService.addOrUpdatePasspointConfiguration(config, mContext.getOpPackageName())) {
throw new IllegalArgumentException();
}
} catch (RemoteException e) {
@@ -1170,7 +1170,7 @@ public class WifiManager {
*/
public void removePasspointConfiguration(String fqdn) {
try {
- if (!mService.removePasspointConfiguration(fqdn)) {
+ if (!mService.removePasspointConfiguration(fqdn, mContext.getOpPackageName())) {
throw new IllegalArgumentException();
}
} catch (RemoteException e) {
@@ -1256,7 +1256,7 @@ public class WifiManager {
*/
public boolean removeNetwork(int netId) {
try {
- return mService.removeNetwork(netId);
+ return mService.removeNetwork(netId, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1302,7 +1302,7 @@ public class WifiManager {
boolean success;
try {
- success = mService.enableNetwork(netId, attemptConnect);
+ success = mService.enableNetwork(netId, attemptConnect, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1328,7 +1328,7 @@ public class WifiManager {
*/
public boolean disableNetwork(int netId) {
try {
- return mService.disableNetwork(netId);
+ return mService.disableNetwork(netId, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1341,7 +1341,7 @@ public class WifiManager {
*/
public boolean disconnect() {
try {
- mService.disconnect();
+ mService.disconnect(mContext.getOpPackageName());
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1356,7 +1356,7 @@ public class WifiManager {
*/
public boolean reconnect() {
try {
- mService.reconnect();
+ mService.reconnect(mContext.getOpPackageName());
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1371,7 +1371,7 @@ public class WifiManager {
*/
public boolean reassociate() {
try {
- mService.reassociate();
+ mService.reassociate(mContext.getOpPackageName());
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1701,7 +1701,7 @@ public class WifiManager {
@Deprecated
public boolean saveConfiguration() {
try {
- return mService.saveConfiguration();
+ return mService.saveConfiguration(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2136,7 +2136,7 @@ public class WifiManager {
@RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
public boolean setWifiApConfiguration(WifiConfiguration wifiConfig) {
try {
- mService.setWifiApConfiguration(wifiConfig);
+ mService.setWifiApConfiguration(wifiConfig, mContext.getOpPackageName());
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -3052,7 +3052,7 @@ public class WifiManager {
public void disableEphemeralNetwork(String SSID) {
if (SSID == null) throw new IllegalArgumentException("SSID cannot be null");
try {
- mService.disableEphemeralNetwork(SSID);
+ mService.disableEphemeralNetwork(SSID, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3093,7 +3093,7 @@ public class WifiManager {
*/
public Messenger getWifiServiceMessenger() {
try {
- return mService.getWifiServiceMessenger();
+ return mService.getWifiServiceMessenger(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3619,7 +3619,7 @@ public class WifiManager {
*/
public void factoryReset() {
try {
- mService.factoryReset();
+ mService.factoryReset(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}