diff options
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(); } |