summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java3
-rw-r--r--core/api/current.txt2
-rw-r--r--core/api/system-current.txt10
-rw-r--r--core/java/android/app/Activity.java2
-rw-r--r--core/java/android/app/ActivityOptions.java118
-rw-r--r--core/java/android/app/ActivityTransitionState.java12
-rw-r--r--core/java/android/app/AppOpsManager.java2
-rw-r--r--core/java/android/app/ExitTransitionCoordinator.java122
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java165
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java9
-rw-r--r--core/java/android/util/DebugUtils.java20
-rw-r--r--core/java/com/android/internal/util/FastXmlSerializer.java7
-rw-r--r--core/res/AndroidManifest.xml10
-rw-r--r--keystore/java/android/security/AppUriAuthenticationPolicy.java15
-rw-r--r--keystore/java/android/security/IKeyChainService.aidl9
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java122
-rw-r--r--services/people/java/com/android/server/people/PeopleService.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java2
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java37
-rw-r--r--telephony/java/android/telephony/ims/ImsCallProfile.java76
-rw-r--r--wifi/aidl-export/android/net/wifi/CoexUnsafeChannel.aidl19
-rw-r--r--wifi/api/system-current.txt24
-rw-r--r--wifi/java/android/net/wifi/CoexUnsafeChannel.java176
-rw-r--r--wifi/java/android/net/wifi/ICoexCallback.aidl26
-rw-r--r--wifi/java/android/net/wifi/IWifiManager.aidl12
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java234
-rw-r--r--wifi/tests/src/android/net/wifi/CoexUnsafeChannelTest.java97
-rw-r--r--wifi/tests/src/android/net/wifi/WifiManagerTest.java147
28 files changed, 1273 insertions, 214 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
index 56b97f2e5757..2b5aab83463b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
@@ -282,12 +282,13 @@ public final class TimeController extends StateController {
String nextDelayPackageName = null;
boolean ready = false;
Iterator<JobStatus> it = mTrackedJobs.iterator();
+ final long nowElapsedMillis = sElapsedRealtimeClock.millis();
while (it.hasNext()) {
final JobStatus job = it.next();
if (!job.hasTimingDelayConstraint()) {
continue;
}
- if (evaluateTimingDelayConstraint(job, sElapsedRealtimeClock.millis())) {
+ if (evaluateTimingDelayConstraint(job, nowElapsedMillis)) {
if (canStopTrackingJobLocked(job)) {
it.remove();
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 07f839571c92..9cec7a67d6ec 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -45128,6 +45128,7 @@ package android.telephony {
field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
field public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1; // 0xffffffff
field public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool";
+ field public static final String EXTRA_REBROADCAST_ON_UNLOCK = "android.telephony.extra.REBROADCAST_ON_UNLOCK";
field public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
field public static final String IMSI_KEY_AVAILABILITY_INT = "imsi_key_availability_int";
@@ -45402,6 +45403,7 @@ package android.telephony {
}
public static final class CarrierConfigManager.Ims {
+ field public static final String KEY_ENABLE_PRESENCE_PUBLISH_BOOL = "ims.enable_presence_publish_bool";
field public static final String KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL = "ims.ims_single_registration_required_bool";
field public static final String KEY_PREFIX = "ims.";
field public static final String KEY_WIFI_OFF_DEFERRING_TIME_MILLIS_INT = "ims.wifi_off_deferring_time_millis_int";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 8ba5a94179ea..a69f4562b751 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -248,7 +248,9 @@ package android {
field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
+ field public static final String WIFI_ACCESS_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS";
field public static final String WIFI_SET_DEVICE_MOBILITY_STATE = "android.permission.WIFI_SET_DEVICE_MOBILITY_STATE";
+ field public static final String WIFI_UPDATE_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS";
field public static final String WIFI_UPDATE_USABILITY_STATS_SCORE = "android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE";
field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
field public static final String WRITE_DREAM_STATE = "android.permission.WRITE_DREAM_STATE";
@@ -11525,6 +11527,7 @@ package android.telephony.ims {
method public boolean getCallExtraBoolean(String, boolean);
method public int getCallExtraInt(String);
method public int getCallExtraInt(String, int);
+ method @Nullable public <T extends android.os.Parcelable> T getCallExtraParcelable(@Nullable String);
method public android.os.Bundle getCallExtras();
method public int getCallType();
method public static int getCallTypeFromVideoState(int);
@@ -11547,6 +11550,7 @@ package android.telephony.ims {
method public void setCallExtra(String, String);
method public void setCallExtraBoolean(String, boolean);
method public void setCallExtraInt(String, int);
+ method public void setCallExtraParcelable(@NonNull String, @NonNull android.os.Parcelable);
method public void setCallRestrictCause(int);
method public void setCallerNumberVerificationStatus(int);
method public void setEmergencyCallRouting(int);
@@ -11581,6 +11585,7 @@ package android.telephony.ims {
field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telephony.ims.extra.CALL_DISCONNECT_CAUSE";
field public static final String EXTRA_CALL_NETWORK_TYPE = "android.telephony.ims.extra.CALL_NETWORK_TYPE";
field @Deprecated public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
+ field public static final String EXTRA_CALL_SUBJECT = "android.telephony.ims.extra.CALL_SUBJECT";
field public static final String EXTRA_CHILD_NUMBER = "ChildNum";
field public static final String EXTRA_CNA = "cna";
field public static final String EXTRA_CNAP = "cnap";
@@ -11590,8 +11595,11 @@ package android.telephony.ims {
field public static final String EXTRA_EMERGENCY_CALL = "e_call";
field public static final String EXTRA_FORWARDED_NUMBER = "android.telephony.ims.extra.FORWARDED_NUMBER";
field public static final String EXTRA_IS_CALL_PULL = "CallPull";
+ field public static final String EXTRA_LOCATION = "android.telephony.ims.extra.LOCATION";
field public static final String EXTRA_OI = "oi";
field public static final String EXTRA_OIR = "oir";
+ field public static final String EXTRA_PICTURE_URL = "android.telephony.ims.extra.PICTURE_URL";
+ field public static final String EXTRA_PRIORITY = "android.telephony.ims.extra.PRIORITY";
field public static final String EXTRA_REMOTE_URI = "remote_uri";
field public static final String EXTRA_USSD = "ussd";
field public static final int OIR_DEFAULT = 0; // 0x0
@@ -11599,6 +11607,8 @@ package android.telephony.ims {
field public static final int OIR_PRESENTATION_PAYPHONE = 4; // 0x4
field public static final int OIR_PRESENTATION_RESTRICTED = 1; // 0x1
field public static final int OIR_PRESENTATION_UNKNOWN = 3; // 0x3
+ field public static final int PRIORITY_NORMAL = 0; // 0x0
+ field public static final int PRIORITY_URGENT = 1; // 0x1
field public static final int SERVICE_TYPE_EMERGENCY = 2; // 0x2
field public static final int SERVICE_TYPE_NONE = 0; // 0x0
field public static final int SERVICE_TYPE_NORMAL = 1; // 0x1
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 1c2f682c9492..5ee5597e1984 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2590,7 +2590,7 @@ public class Activity extends ContextThemeWrapper
protected void onStop() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this);
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
- mActivityTransitionState.onStop();
+ mActivityTransitionState.onStop(this);
dispatchActivityStopped();
mTranslucentCallback = null;
mCalled = true;
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index ef146b118c33..0b0781e917e3 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -27,6 +27,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
+import android.app.ExitTransitionCoordinator.ActivityExitTransitionCallbacks;
+import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -44,8 +46,6 @@ import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.UserHandle;
-import android.transition.Transition;
-import android.transition.TransitionListenerAdapter;
import android.transition.TransitionManager;
import android.util.Pair;
import android.util.Slog;
@@ -806,8 +806,11 @@ public class ActivityOptions {
public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
Pair<View, String>... sharedElements) {
ActivityOptions opts = new ActivityOptions();
- makeSceneTransitionAnimation(activity, activity.getWindow(), opts,
- activity.mExitTransitionListener, sharedElements);
+ ExitTransitionCoordinator exit = makeSceneTransitionAnimation(
+ new ActivityExitTransitionCallbacks(activity), activity.mExitTransitionListener,
+ activity.getWindow(), opts, sharedElements);
+ opts.mExitCoordinatorIndex =
+ activity.mActivityTransitionState.addExitTransitionCoordinator(exit);
return opts;
}
@@ -823,25 +826,19 @@ public class ActivityOptions {
* @hide
*/
@SafeVarargs
- public static ActivityOptions startSharedElementAnimation(Window window,
+ public static Pair<ActivityOptions, ExitTransitionCoordinator> startSharedElementAnimation(
+ Window window, ExitTransitionCallbacks exitCallbacks, SharedElementCallback callback,
Pair<View, String>... sharedElements) {
ActivityOptions opts = new ActivityOptions();
- final View decorView = window.getDecorView();
- if (decorView == null) {
- return opts;
- }
- final ExitTransitionCoordinator exit =
- makeSceneTransitionAnimation(null, window, opts, null, sharedElements);
- if (exit != null) {
- HideWindowListener listener = new HideWindowListener(window, exit);
- exit.setHideSharedElementsCallback(listener);
- exit.startExit();
- }
- return opts;
+ ExitTransitionCoordinator exit = makeSceneTransitionAnimation(
+ exitCallbacks, callback, window, opts, sharedElements);
+ opts.mExitCoordinatorIndex = -1;
+ return Pair.create(opts, exit);
}
/**
- * This method should be called when the {@link #startSharedElementAnimation(Window, Pair[])}
+ * This method should be called when the
+ * {@link #startSharedElementAnimation(Window, ExitTransitionCallbacks, Pair[])}
* animation must be stopped and the Views reset. This can happen if there was an error
* from startActivity or a springboard activity and the animation should stop and reset.
*
@@ -864,9 +861,9 @@ public class ActivityOptions {
}
}
- static ExitTransitionCoordinator makeSceneTransitionAnimation(Activity activity, Window window,
- ActivityOptions opts, SharedElementCallback callback,
- Pair<View, String>[] sharedElements) {
+ static ExitTransitionCoordinator makeSceneTransitionAnimation(
+ ExitTransitionCallbacks exitCallbacks, SharedElementCallback callback, Window window,
+ ActivityOptions opts, Pair<View, String>[] sharedElements) {
if (!window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) {
opts.mAnimationType = ANIM_DEFAULT;
return null;
@@ -892,17 +889,11 @@ public class ActivityOptions {
}
}
- ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, window,
+ ExitTransitionCoordinator exit = new ExitTransitionCoordinator(exitCallbacks, window,
callback, names, names, views, false);
opts.mTransitionReceiver = exit;
opts.mSharedElementNames = names;
- opts.mIsReturning = (activity == null);
- if (activity == null) {
- opts.mExitCoordinatorIndex = -1;
- } else {
- opts.mExitCoordinatorIndex =
- activity.mActivityTransitionState.addExitTransitionCoordinator(exit);
- }
+ opts.mIsReturning = false;
return exit;
}
@@ -928,8 +919,12 @@ public class ActivityOptions {
opts.mIsReturning = true;
opts.mResultCode = resultCode;
opts.mResultData = resultData;
- opts.mExitCoordinatorIndex =
- activity.mActivityTransitionState.addExitTransitionCoordinator(exitCoordinator);
+ if (activity == null) {
+ opts.mExitCoordinatorIndex = -1;
+ } else {
+ opts.mExitCoordinatorIndex =
+ activity.mActivityTransitionState.addExitTransitionCoordinator(exitCoordinator);
+ }
return opts;
}
@@ -1868,67 +1863,6 @@ public class ActivityOptions {
+ mStartY + ", mWidth=" + mWidth + ", mHeight=" + mHeight;
}
- private static class HideWindowListener extends TransitionListenerAdapter
- implements ExitTransitionCoordinator.HideSharedElementsCallback {
- private final Window mWindow;
- private final ExitTransitionCoordinator mExit;
- private final boolean mWaitingForTransition;
- private boolean mTransitionEnded;
- private boolean mSharedElementHidden;
- private ArrayList<View> mSharedElements;
-
- public HideWindowListener(Window window, ExitTransitionCoordinator exit) {
- mWindow = window;
- mExit = exit;
- mSharedElements = new ArrayList<>(exit.mSharedElements);
- Transition transition = mWindow.getExitTransition();
- if (transition != null) {
- transition.addListener(this);
- mWaitingForTransition = true;
- } else {
- mWaitingForTransition = false;
- }
- View decorView = mWindow.getDecorView();
- if (decorView != null) {
- if (decorView.getTag(com.android.internal.R.id.cross_task_transition) != null) {
- throw new IllegalStateException(
- "Cannot start a transition while one is running");
- }
- decorView.setTagInternal(com.android.internal.R.id.cross_task_transition, exit);
- }
- }
-
- @Override
- public void onTransitionEnd(Transition transition) {
- mTransitionEnded = true;
- hideWhenDone();
- transition.removeListener(this);
- }
-
- @Override
- public void hideSharedElements() {
- mSharedElementHidden = true;
- hideWhenDone();
- }
-
- private void hideWhenDone() {
- if (mSharedElementHidden && (!mWaitingForTransition || mTransitionEnded)) {
- mExit.resetViews();
- int numSharedElements = mSharedElements.size();
- for (int i = 0; i < numSharedElements; i++) {
- View view = mSharedElements.get(i);
- view.requestLayout();
- }
- View decorView = mWindow.getDecorView();
- if (decorView != null) {
- decorView.setTagInternal(
- com.android.internal.R.id.cross_task_transition, null);
- decorView.setVisibility(View.GONE);
- }
- }
- }
- }
-
/**
* The information about the source of activity launch. E.g. describe an activity is launched
* from launcher by receiving a motion event with a timestamp.
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index 5c4125e01dfd..62619509184a 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -247,14 +247,14 @@ class ActivityTransitionState {
mEnterActivityOptions = null;
}
- public void onStop() {
+ public void onStop(Activity activity) {
restoreExitedViews();
if (mEnterTransitionCoordinator != null) {
mEnterTransitionCoordinator.stop();
mEnterTransitionCoordinator = null;
}
if (mReturnExitCoordinator != null) {
- mReturnExitCoordinator.stop();
+ mReturnExitCoordinator.stop(activity);
mReturnExitCoordinator = null;
}
}
@@ -331,7 +331,8 @@ class ActivityTransitionState {
}
}
- mReturnExitCoordinator = new ExitTransitionCoordinator(activity,
+ mReturnExitCoordinator = new ExitTransitionCoordinator(
+ new ExitTransitionCoordinator.ActivityExitTransitionCallbacks(activity),
activity.getWindow(), activity.mEnterTransitionListener, pendingExitNames,
null, null, true);
if (enterViewsTransition != null && decor != null) {
@@ -341,12 +342,11 @@ class ActivityTransitionState {
final ViewGroup finalDecor = decor;
OneShotPreDrawListener.add(decor, () -> {
if (mReturnExitCoordinator != null) {
- mReturnExitCoordinator.startExit(activity.mResultCode,
- activity.mResultData);
+ mReturnExitCoordinator.startExit(activity);
}
});
} else {
- mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData);
+ mReturnExitCoordinator.startExit(activity);
}
}
return true;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index cdfe41e85917..4dd6a7efe7c5 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -358,7 +358,7 @@ public class AppOpsManager {
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true, prefix = { "MODE_" }, value = {
+ @IntDef(prefix = { "MODE_" }, value = {
MODE_ALLOWED,
MODE_IGNORED,
MODE_ERRORED,
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 68824cd26eaa..9fdff5979cd0 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -18,6 +18,7 @@ package android.app;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
+import android.annotation.NonNull;
import android.app.SharedElementCallback.OnSharedElementsReadyListener;
import android.content.Intent;
import android.graphics.Color;
@@ -45,15 +46,17 @@ import java.util.ArrayList;
* This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation
* to govern the exit of the Scene and the shared elements when calling an Activity as well as
* the reentry of the Scene when coming back from the called Activity.
+ *
+ * @hide
*/
-class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
+public class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
private static final String TAG = "ExitTransitionCoordinator";
static long sMaxWaitMillis = 1000;
private Bundle mSharedElementBundle;
private boolean mExitNotified;
private boolean mSharedElementNotified;
- private Activity mActivity;
+ private ExitTransitionCallbacks mExitCallbacks;
private boolean mIsBackgroundReady;
private boolean mIsCanceled;
private Handler mHandler;
@@ -62,20 +65,15 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
private Bundle mExitSharedElementBundle;
private boolean mIsExitStarted;
private boolean mSharedElementsHidden;
- private HideSharedElementsCallback mHideSharedElementsCallback;
- public ExitTransitionCoordinator(Activity activity, Window window,
- SharedElementCallback listener, ArrayList<String> names,
+ public ExitTransitionCoordinator(ExitTransitionCallbacks exitCallbacks,
+ Window window, SharedElementCallback listener, ArrayList<String> names,
ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) {
super(window, names, listener, isReturning);
viewsReady(mapSharedElements(accepted, mapped));
stripOffscreenViews();
mIsBackgroundReady = !isReturning;
- mActivity = activity;
- }
-
- void setHideSharedElementsCallback(HideSharedElementsCallback callback) {
- mHideSharedElementsCallback = callback;
+ mExitCallbacks = exitCallbacks;
}
@Override
@@ -190,8 +188,8 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
private void hideSharedElements() {
moveSharedElementsFromOverlay();
- if (mHideSharedElementsCallback != null) {
- mHideSharedElementsCallback.hideSharedElements();
+ if (mExitCallbacks != null) {
+ mExitCallbacks.hideSharedElements();
}
if (!mIsHidden) {
hideViews(mSharedElements);
@@ -210,20 +208,16 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
decorView.suppressLayout(true);
}
moveSharedElementsToOverlay();
- startTransition(new Runnable() {
- @Override
- public void run() {
- if (mActivity != null) {
- beginTransitions();
- } else {
- startExitTransition();
- }
- }
- });
+ startTransition(this::beginTransitions);
}
}
- public void startExit(int resultCode, Intent data) {
+ /**
+ * Starts the exit animation and sends back the activity result
+ */
+ public void startExit(Activity activity) {
+ int resultCode = activity.mResultCode;
+ Intent data = activity.mResultData;
if (!mIsExitStarted) {
mIsExitStarted = true;
pauseInput();
@@ -247,9 +241,9 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
.getApplicationInfo().targetSdkVersion >= VERSION_CODES.M;
ArrayList<String> sharedElementNames = targetsM ? mSharedElementNames :
mAllSharedElementNames;
- ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this,
+ ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(activity, this,
sharedElementNames, resultCode, data);
- mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
+ activity.convertToTranslucent(new Activity.TranslucentConversionListener() {
@Override
public void onTranslucentConversionComplete(boolean drawComplete) {
if (!mIsCanceled) {
@@ -257,21 +251,19 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
}
}
}, options);
- startTransition(new Runnable() {
- @Override
- public void run() {
- startExitTransition();
- }
- });
+ startTransition(this::startExitTransition);
}
}
- public void stop() {
- if (mIsReturning && mActivity != null) {
+ /**
+ * Called from {@link Activity#onStop()}
+ */
+ public void stop(Activity activity) {
+ if (mIsReturning && mExitCallbacks != null) {
// Override the previous ActivityOptions. We don't want the
// activity to have options since we're essentially canceling the
// transition and finishing right now.
- mActivity.convertToTranslucent(null, null);
+ activity.convertToTranslucent(null, null);
finish();
}
}
@@ -434,7 +426,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
mSharedElementNotified = true;
delayCancel();
- if (!mActivity.isTopOfTask()) {
+ if (mExitCallbacks.isReturnTransitionAllowed()) {
mResultReceiver.send(MSG_ALLOW_RETURN_TRANSITION, null);
}
@@ -474,22 +466,20 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
}
private void finishIfNecessary() {
- if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() ||
- mSharedElementsHidden)) {
+ if (mIsReturning && mExitNotified && mExitCallbacks != null && (mSharedElements.isEmpty()
+ || mSharedElementsHidden)) {
finish();
}
if (!mIsReturning && mExitNotified) {
- mActivity = null; // don't need it anymore
+ mExitCallbacks = null; // don't need it anymore
}
}
private void finish() {
stopCancel();
- if (mActivity != null) {
- mActivity.mActivityTransitionState.clear();
- mActivity.finish();
- mActivity.overridePendingTransition(0, 0);
- mActivity = null;
+ if (mExitCallbacks != null) {
+ mExitCallbacks.onFinish();
+ mExitCallbacks = null;
}
// Clear the state so that we can't hold any references accidentally and leak memory.
clearState();
@@ -529,7 +519,49 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
}
}
- interface HideSharedElementsCallback {
- void hideSharedElements();
+ /**
+ * @hide
+ */
+ public interface ExitTransitionCallbacks {
+
+ /**
+ * Returns true if reverse exit animation is supported
+ */
+ boolean isReturnTransitionAllowed();
+
+ /**
+ * Called then the transition finishes
+ */
+ void onFinish();
+
+ /**
+ * Optional callback when the transition is hiding elements in the source surface
+ */
+ default void hideSharedElements() { };
+ }
+
+ /**
+ * @hide
+ */
+ public static class ActivityExitTransitionCallbacks implements ExitTransitionCallbacks {
+
+ @NonNull
+ final Activity mActivity;
+
+ ActivityExitTransitionCallbacks(@NonNull Activity activity) {
+ mActivity = activity;
+ }
+
+ @Override
+ public boolean isReturnTransitionAllowed() {
+ return !mActivity.isTopOfTask();
+ }
+
+ @Override
+ public void onFinish() {
+ mActivity.mActivityTransitionState.clear();
+ mActivity.finish();
+ mActivity.overridePendingTransition(0, 0);
+ }
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 26784f2c247e..5eb1922a163c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -87,6 +87,7 @@ import android.service.restrictions.RestrictionsReceiver;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
import android.util.ArraySet;
+import android.util.DebugUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -2453,19 +2454,45 @@ public class DevicePolicyManager {
@Retention(RetentionPolicy.SOURCE)
public @interface PersonalAppsSuspensionReason {}
+ // TODO(b/172376923) - make all (or none) @TestApi
+
/** @hide */
@TestApi
public static final int OPERATION_LOCK_NOW = 1;
+ /** @hide */
+ public static final int OPERATION_SWITCH_USER = 2;
+ /** @hide */
+ public static final int OPERATION_START_USER_IN_BACKGROUND = 3;
+ /** @hide */
+ public static final int OPERATION_STOP_USER = 4;
+ /** @hide */
+ public static final int OPERATION_CREATE_AND_MANAGE_USER = 5;
+ /** @hide */
+ public static final int OPERATION_REMOVE_USER = 6;
+
+ private static final String PREFIX_OPERATION = "OPERATION_";
+
+
// TODO(b/172376923) - add all operations
/** @hide */
- @IntDef(prefix = "OPERATION_", value = {
+ @IntDef(prefix = PREFIX_OPERATION, value = {
OPERATION_LOCK_NOW,
+ OPERATION_SWITCH_USER,
+ OPERATION_START_USER_IN_BACKGROUND,
+ OPERATION_STOP_USER,
+ OPERATION_CREATE_AND_MANAGE_USER,
+ OPERATION_REMOVE_USER
})
@Retention(RetentionPolicy.SOURCE)
public static @interface DevicePolicyOperation {
}
+ /** @hide */
+ public static String operationToString(@DevicePolicyOperation int operation) {
+ return DebugUtils.constantToString(DevicePolicyManager.class, PREFIX_OPERATION, operation);
+ }
+
/**
* Return true if the given administrator component is currently active (enabled) in the system.
*
@@ -5213,9 +5240,22 @@ public class DevicePolicyManager {
}
/**
- * Called by a device or profile owner, or delegated certificate installer, to install a
- * certificate and corresponding private key. All apps within the profile will be able to access
- * the certificate and use the private key, given direct user approval.
+ * This API can be called by the following to install a certificate and corresponding
+ * private key:
+ * <ul>
+ * <li>Device owner</li>
+ * <li>Profile owner</li>
+ * <li>Delegated certificate installer</li>
+ * <li>Credential management app</li>
+ * </ul>
+ * All apps within the profile will be able to access the certificate and use the private key,
+ * given direct user approval.
+ *
+ * <p>From Android {@link android.os.Build.VERSION_CODES#S}, the credential management app
+ * can call this API. However, this API sets the key pair as user selectable by default,
+ * which is not permitted when called by the credential management app. Instead,
+ * {@link #installKeyPair(ComponentName, PrivateKey, Certificate[], String, int)} should be
+ * called with {@link #INSTALLKEY_SET_USER_SELECTABLE} not set as a flag.
*
* <p>Access to the installed credentials will not be granted to the caller of this API without
* direct user approval. This is for security - should a certificate installer become
@@ -5246,10 +5286,23 @@ public class DevicePolicyManager {
}
/**
- * Called by a device or profile owner, or delegated certificate installer, to install a
- * certificate chain and corresponding private key for the leaf certificate. All apps within the
- * profile will be able to access the certificate chain and use the private key, given direct
- * user approval.
+ * This API can be called by the following to install a certificate chain and corresponding
+ * private key for the leaf certificate:
+ * <ul>
+ * <li>Device owner</li>
+ * <li>Profile owner</li>
+ * <li>Delegated certificate installer</li>
+ * <li>Credential management app</li>
+ * </ul>
+ * All apps within the profile will be able to access the certificate chain and use the private
+ * key, given direct user approval.
+ *
+ * <p>From Android {@link android.os.Build.VERSION_CODES#S}, the credential management app
+ * can call this API. However, this API sets the key pair as user selectable by default,
+ * which is not permitted when called by the credential management app. Instead,
+ * {@link #installKeyPair(ComponentName, PrivateKey, Certificate[], String, int)} should be
+ * called with {@link #INSTALLKEY_SET_USER_SELECTABLE} not set as a flag.
+ * Note, there can only be a credential management app on an unmanaged device.
*
* <p>The caller of this API may grant itself access to the certificate and private key
* immediately, without user approval. It is a best practice not to request this unless strictly
@@ -5287,10 +5340,26 @@ public class DevicePolicyManager {
}
/**
- * Called by a device or profile owner, or delegated certificate installer, to install a
- * certificate chain and corresponding private key for the leaf certificate. All apps within the
- * profile will be able to access the certificate chain and use the private key, given direct
- * user approval (if the user is allowed to select the private key).
+ * This API can be called by the following to install a certificate chain and corresponding
+ * private key for the leaf certificate:
+ * <ul>
+ * <li>Device owner</li>
+ * <li>Profile owner</li>
+ * <li>Delegated certificate installer</li>
+ * <li>Credential management app</li>
+ * </ul>
+ * All apps within the profile will be able to access the certificate chain and use the
+ * private key, given direct user approval (if the user is allowed to select the private key).
+ *
+ * <p>From Android {@link android.os.Build.VERSION_CODES#S}, the credential management app
+ * can call this API. If called by the credential management app:
+ * <ul>
+ * <li>The componentName must be {@code null}r</li>
+ * <li>The alias must exist in the credential management app's
+ * {@link android.security.AppUriAuthenticationPolicy}</li>
+ * <li>The key pair must not be user selectable</li>
+ * </ul>
+ * Note, there can only be a credential management app on an unmanaged device.
*
* <p>The caller of this API may grant itself access to the certificate and private key
* immediately, without user approval. It is a best practice not to request this unless strictly
@@ -5316,7 +5385,8 @@ public class DevicePolicyManager {
* {@link #INSTALLKEY_REQUEST_CREDENTIALS_ACCESS}.
* @return {@code true} if the keys were installed, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
- * owner.
+ * owner, or {@code admin} is null but the calling application is not a delegated
+ * certificate installer or credential management app.
* @see android.security.KeyChain#getCertificateChain
* @see #setDelegatedScopes
* @see #DELEGATION_CERT_INSTALL
@@ -5349,15 +5419,26 @@ public class DevicePolicyManager {
}
/**
- * Called by a device or profile owner, or delegated certificate installer, to remove a
- * certificate and private key pair installed under a given alias.
+ * This API can be called by the following to remove a certificate and private key pair
+ * installed under a given alias:
+ * <ul>
+ * <li>Device owner</li>
+ * <li>Profile owner</li>
+ * <li>Delegated certificate installer</li>
+ * <li>Credential management app</li>
+ * </ul>
+ *
+ * <p>From Android {@link android.os.Build.VERSION_CODES#S}, the credential management app
+ * can call this API. If called by the credential management app, the componentName must be
+ * {@code null}. Note, there can only be a credential management app on an unmanaged device.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
* {@code null} if calling from a delegated certificate installer.
* @param alias The private key alias under which the certificate is installed.
* @return {@code true} if the private key alias no longer exists, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
- * owner.
+ * owner, or {@code admin} is null but the calling application is not a delegated
+ * certificate installer or credential management app.
* @see #setDelegatedScopes
* @see #DELEGATION_CERT_INSTALL
*/
@@ -5392,10 +5473,20 @@ public class DevicePolicyManager {
}
/**
- * Called by a device or profile owner, or delegated certificate installer, to generate a
- * new private/public key pair. If the device supports key generation via secure hardware,
- * this method is useful for creating a key in KeyChain that never left the secure hardware.
- * Access to the key is controlled the same way as in {@link #installKeyPair}.
+ * This API can be called by the following to generate a new private/public key pair:
+ * <ul>
+ * <li>Device owner</li>
+ * <li>Profile owner</li>
+ * <li>Delegated certificate installer</li>
+ * <li>Credential management app</li>
+ * </ul>
+ * If the device supports key generation via secure hardware, this method is useful for
+ * creating a key in KeyChain that never left the secure hardware. Access to the key is
+ * controlled the same way as in {@link #installKeyPair}.
+ *
+ * <p>From Android {@link android.os.Build.VERSION_CODES#S}, the credential management app
+ * can call this API. If called by the credential management app, the componentName must be
+ * {@code null}. Note, there can only be a credential management app on an unmanaged device.
*
* <p>Because this method might take several seconds to complete, it should only be called from
* a worker thread. This method returns {@code null} when called from the main thread.
@@ -5418,9 +5509,10 @@ public class DevicePolicyManager {
* supports these features, refer to {@link #isDeviceIdAttestationSupported()} and
* {@link #isUniqueDeviceAttestationSupported()}.
*
- * <p>Device owner, profile owner and their delegated certificate installer can use
- * {@link #ID_TYPE_BASE_INFO} to request inclusion of the general device information
- * including manufacturer, model, brand, device and product in the attestation record.
+ * <p>Device owner, profile owner, their delegated certificate installer and the credential
+ * management app can use {@link #ID_TYPE_BASE_INFO} to request inclusion of the general device
+ * information including manufacturer, model, brand, device and product in the attestation
+ * record.
* Only device owner, profile owner on an organization-owned device and their delegated
* certificate installers can use {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and
* {@link #ID_TYPE_MEID} to request unique device identifiers to be attested (the serial number,
@@ -5455,9 +5547,11 @@ public class DevicePolicyManager {
* {@code keySpec}.
* @return A non-null {@code AttestedKeyPair} if the key generation succeeded, null otherwise.
* @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
- * owner. If Device ID attestation is requested (using {@link #ID_TYPE_SERIAL},
- * {@link #ID_TYPE_IMEI} or {@link #ID_TYPE_MEID}), the caller must be the Device Owner
- * or the Certificate Installer delegate.
+ * owner, or {@code admin} is null but the calling application is not a delegated
+ * certificate installer or credential management app. If Device ID attestation is
+ * requested (using {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} or
+ * {@link #ID_TYPE_MEID}), the caller must be the Device Owner or the Certificate
+ * Installer delegate.
* @throws IllegalArgumentException in the following cases:
* <p>
* <ul>
@@ -5620,10 +5714,19 @@ public class DevicePolicyManager {
}
/**
- * Called by a device or profile owner, or delegated certificate installer, to associate
- * certificates with a key pair that was generated using {@link #generateKeyPair}, and
- * set whether the key is available for the user to choose in the certificate selection
- * prompt.
+ * This API can be called by the following to associate certificates with a key pair that was
+ * generated using {@link #generateKeyPair}, and set whether the key is available for the user
+ * to choose in the certificate selection prompt:
+ * <ul>
+ * <li>Device owner</li>
+ * <li>Profile owner</li>
+ * <li>Delegated certificate installer</li>
+ * <li>Credential management app</li>
+ * </ul>
+ *
+ * <p>From Android {@link android.os.Build.VERSION_CODES#S}, the credential management app
+ * can call this API. If called by the credential management app, the componentName must be
+ * {@code null}. Note, there can only be a credential management app on an unmanaged device.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
* {@code null} if calling from a delegated certificate installer.
@@ -5641,7 +5744,7 @@ public class DevicePolicyManager {
* successfully associated with it, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
* owner, or {@code admin} is null but the calling application is not a delegated
- * certificate installer.
+ * certificate installer or credential management app.
*/
public boolean setKeyPairCertificate(@Nullable ComponentName admin,
@NonNull String alias, @NonNull List<Certificate> certs, boolean isUserSelectable) {
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 9a76f19f3e41..37637119c70a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -199,6 +199,7 @@ public abstract class WallpaperService extends Service {
final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0];
final MergedConfiguration mMergedConfiguration = new MergedConfiguration();
private final Point mSurfaceSize = new Point();
+ private final Point mLastSurfaceSize = new Point();
private final Matrix mTmpMatrix = new Matrix();
private final float[] mTmpValues = new float[9];
@@ -908,6 +909,14 @@ public abstract class WallpaperService extends Service {
if (mSurfaceControl.isValid()) {
mSurfaceHolder.mSurface.copyFrom(mSurfaceControl);
}
+ if (!mLastSurfaceSize.equals(mSurfaceSize)) {
+ mLastSurfaceSize.set(mSurfaceSize.x, mSurfaceSize.y);
+ if (mSurfaceControl != null && mSurfaceControl.isValid()) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setBufferSize(mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y);
+ t.apply();
+ }
+ }
if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface
+ ", frame=" + mWinFrames);
diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java
index 6d5e830346cd..ece6b3516f7a 100644
--- a/core/java/android/util/DebugUtils.java
+++ b/core/java/android/util/DebugUtils.java
@@ -271,6 +271,26 @@ public class DebugUtils {
return res.toString();
}
+ /**
+ * Gets human-readable representation of constants (static final values).
+ *
+ * @hide
+ */
+ public static String constantToString(Class<?> clazz, String prefix, int value) {
+ for (Field field : clazz.getDeclaredFields()) {
+ final int modifiers = field.getModifiers();
+ try {
+ if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
+ && field.getType().equals(int.class) && field.getName().startsWith(prefix)
+ && field.getInt(null) == value) {
+ return constNameWithoutPrefix(prefix, field);
+ }
+ } catch (IllegalAccessException ignored) {
+ }
+ }
+ return prefix + Integer.toString(value);
+ }
+
private static String constNameWithoutPrefix(String prefix, Field field) {
return field.getName().substring(prefix.length());
}
diff --git a/core/java/com/android/internal/util/FastXmlSerializer.java b/core/java/com/android/internal/util/FastXmlSerializer.java
index 94e07a85867e..929c9e8bb8c1 100644
--- a/core/java/com/android/internal/util/FastXmlSerializer.java
+++ b/core/java/com/android/internal/util/FastXmlSerializer.java
@@ -367,8 +367,11 @@ public class FastXmlSerializer implements XmlSerializer {
public void startDocument(String encoding, Boolean standalone) throws IOException,
IllegalArgumentException, IllegalStateException {
- append("<?xml version='1.0' encoding='utf-8' standalone='"
- + (standalone ? "yes" : "no") + "' ?>\n");
+ append("<?xml version='1.0' encoding='utf-8'");
+ if (standalone != null) {
+ append(" standalone='" + (standalone ? "yes" : "no") + "'");
+ }
+ append(" ?>\n");
mLineStart = true;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5a6905699535..60dd0eb8a780 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1815,6 +1815,16 @@
<permission android:name="android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi @hide Allows system APK to update Wifi/Cellular coex channels to avoid.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows applications to access Wifi/Cellular coex channels being avoided.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS"
+ android:protectionLevel="signature|privileged" />
+
<!-- ======================================= -->
<!-- Permissions for short range, peripheral networks -->
<!-- ======================================= -->
diff --git a/keystore/java/android/security/AppUriAuthenticationPolicy.java b/keystore/java/android/security/AppUriAuthenticationPolicy.java
index 30f5a94ca0c8..0244ce97c0d4 100644
--- a/keystore/java/android/security/AppUriAuthenticationPolicy.java
+++ b/keystore/java/android/security/AppUriAuthenticationPolicy.java
@@ -28,8 +28,10 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
/**
* The app-URI authentication policy is set by the credential management app. This policy determines
@@ -223,4 +225,17 @@ public final class AppUriAuthenticationPolicy implements Parcelable {
}
}
+ /**
+ * Get the set of aliases found in the policy.
+ *
+ * @hide
+ */
+ public Set<String> getAliases() {
+ Set<String> aliases = new HashSet<>();
+ for (UrisToAliases appsToUris : mAppToUris.values()) {
+ aliases.addAll(appsToUris.getUrisToAliases().values());
+ }
+ return aliases;
+ }
+
}
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index 1ae6a631dbcb..add52fa5b436 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -18,6 +18,8 @@ package android.security;
import android.content.pm.StringParceledListSlice;
import android.security.keymaster.KeymasterCertificateChain;
import android.security.keystore.ParcelableKeyGenParameterSpec;
+import android.security.AppUriAuthenticationPolicy;
+import android.net.Uri;
/**
* Caller is required to ensure that {@link KeyStore#unlock
@@ -56,6 +58,13 @@ interface IKeyChainService {
boolean containsCaAlias(String alias);
byte[] getEncodedCaCertificate(String alias, boolean includeDeletedSystem);
List<String> getCaCertificateChainAliases(String rootAlias, boolean includeDeletedSystem);
+ void setCredentialManagementApp(String packageName, in AppUriAuthenticationPolicy policy);
+ void updateCredentialManagementAppPolicy(in AppUriAuthenticationPolicy policy);
+ boolean hasCredentialManagementApp();
+ String getCredentialManagementAppPackageName();
+ AppUriAuthenticationPolicy getCredentialManagementAppPolicy();
+ String getPredefinedAliasForPackageAndUri(String packageName, in Uri uri);
+ void removeCredentialManagementApp();
// APIs used by KeyChainActivity
void setGrant(int uid, String alias, boolean value);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 5da1706ba23a..823966188b82 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -233,6 +233,7 @@ import android.provider.ContactsInternal;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Telephony;
+import android.security.AppUriAuthenticationPolicy;
import android.security.IKeyChainAliasCallback;
import android.security.IKeyChainService;
import android.security.KeyChain;
@@ -987,29 +988,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
/**
- * Checks if the feature is supported and it's safe to execute the given {@code operation}.
- *
- * <p>Typically called at the beginning of each API method as:
- *
- * <pre><code>
- *
- * if (!canExecute(operation, permission)) return;
- *
- * </code></pre>
- *
- * @return {@code true} when it's safe to execute, {@code false} when the feature is not
- * supported or the caller does not have the given {@code requiredPermission}.
+ * Checks if it's safe to execute the given {@code operation}.
*
* @throws UnsafeStateException if it's not safe to execute the operation.
*/
- boolean canExecute(@DevicePolicyOperation int operation, @NonNull String requiredPermission) {
- if (!mHasFeature && !hasCallingPermission(requiredPermission)) {
- return false;
+ private void checkCanExecuteOrThrowUnsafe(@DevicePolicyOperation int operation) {
+ if (!canExecute(operation)) {
+ throw mSafetyChecker.newUnsafeStateException(operation);
}
- if (mSafetyChecker == null || mSafetyChecker.isDevicePolicyOperationSafe(operation)) {
- return true;
- }
- throw mSafetyChecker.newUnsafeStateException(operation);
+ }
+
+ /**
+ * Returns whether it's safe to execute the given {@code operation}.
+ */
+ boolean canExecute(@DevicePolicyOperation int operation) {
+ return mSafetyChecker == null || mSafetyChecker.isDevicePolicyOperationSafe(operation);
}
/**
@@ -1162,6 +1155,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return LocalServices.getService(PersistentDataBlockManagerInternal.class);
}
+ AppOpsManager getAppOpsManager() {
+ return mContext.getSystemService(AppOpsManager.class);
+ }
+
LockSettingsInternal getLockSettingsInternal() {
return LocalServices.getService(LockSettingsInternal.class);
}
@@ -4845,10 +4842,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void lockNow(int flags, boolean parent) {
- if (!canExecute(DevicePolicyManager.OPERATION_LOCK_NOW, permission.LOCK_DEVICE)) {
- return;
- }
-
final CallerIdentity caller = getCallerIdentity();
final int callingUserId = caller.getUserId();
@@ -4861,6 +4854,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
DeviceAdminInfo.USES_POLICY_FORCE_LOCK,
parent,
android.Manifest.permission.LOCK_DEVICE);
+ checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_LOCK_NOW);
final long ident = mInjector.binderClearCallingIdentity();
try {
adminComponent = admin == null ? null : admin.info.getComponent();
@@ -5042,7 +5036,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
&& (isProfileOwner(caller) || isDeviceOwner(caller)))
- || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL)));
+ || (caller.hasPackage() && (isCallerDelegate(caller, DELEGATION_CERT_INSTALL)
+ || isCredentialManagementApp(caller, alias, isUserSelectable))));
final long id = mInjector.binderClearCallingIdentity();
try {
@@ -5082,7 +5077,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
&& (isProfileOwner(caller) || isDeviceOwner(caller)))
- || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL)));
+ || (caller.hasPackage() && (isCallerDelegate(caller, DELEGATION_CERT_INSTALL)
+ || isCredentialManagementApp(caller, alias))));
final long id = Binder.clearCallingIdentity();
try {
@@ -5286,7 +5282,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
} else {
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
&& (isProfileOwner(caller) || isDeviceOwner(caller)))
- || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL)));
+ || (caller.hasPackage() && (isCallerDelegate(caller, DELEGATION_CERT_INSTALL)
+ || isCredentialManagementApp(caller, alias))));
}
// As the caller will be granted access to the key, ensure no UID was specified, as
@@ -5382,7 +5379,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
&& (isProfileOwner(caller) || isDeviceOwner(caller)))
- || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL)));
+ || (caller.hasPackage() && (isCallerDelegate(caller, DELEGATION_CERT_INSTALL)
+ || isCredentialManagementApp(caller, alias))));
final long id = mInjector.binderClearCallingIdentity();
try (final KeyChainConnection keyChainConnection =
@@ -5817,6 +5815,70 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
+ /**
+ * Check whether a caller application is the credential management app, which can access
+ * privileged APIs.
+ * <p>
+ * This is done by checking that the calling package is authorized to perform the app operation
+ * {@link android.app.AppOpsManager#OP_MANAGE_CREDENTIALS}. The alias provided must be contained
+ * in the aliases specified in the credential management app's authentication policy. The
+ * key pair to install must not be user selectable.
+ *
+ * @param caller the calling identity
+ * @return {@code true} if the calling process is the credential management app.
+ */
+ private boolean isCredentialManagementApp(CallerIdentity caller, String alias,
+ boolean isUserSelectable) {
+ // Should not be user selectable
+ if (isUserSelectable) {
+ Log.e(LOG_TAG, "The credential management app is not allowed to install a "
+ + "user selectable key pair");
+ return false;
+ }
+ return isCredentialManagementApp(caller, alias);
+ }
+
+ /**
+ * Check whether a caller application is the credential mangement app, which can access
+ * privileged APIs.
+ * <p>
+ * This is done by checking that the calling package is authorized to perform the app operation
+ * {@link android.app.AppOpsManager#OP_MANAGE_CREDENTIALS}. The alias provided must be contained
+ * in the aliases specified in the credential management app's authentication policy.
+ *
+ * @param caller the calling identity
+ * @return {@code true} if the calling process is the credential management app.
+ */
+ private boolean isCredentialManagementApp(CallerIdentity caller, String alias) {
+ // Should include alias in authentication policy
+ try (KeyChainConnection connection = KeyChain.bindAsUser(mContext,
+ caller.getUserHandle())) {
+ if (!containsAlias(connection.getService().getCredentialManagementAppPolicy(), alias)) {
+ return false;
+ }
+ } catch (RemoteException | InterruptedException e) {
+ return false;
+ }
+
+ AppOpsManager appOpsManager = mInjector.getAppOpsManager();
+ return appOpsManager != null
+ ? appOpsManager.noteOpNoThrow(AppOpsManager.OP_MANAGE_CREDENTIALS, caller.getUid(),
+ caller.getPackageName(), null, null) == AppOpsManager.MODE_ALLOWED
+ : false;
+ }
+
+ private static boolean containsAlias(AppUriAuthenticationPolicy policy, String alias) {
+ for (Map.Entry<String, Map<Uri, String>> appsToUris :
+ policy.getAppAndUriMappings().entrySet()) {
+ for (Map.Entry<Uri, String> urisToAliases : appsToUris.getValue().entrySet()) {
+ if (urisToAliases.getValue().equals(alias)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
@Override
public void setCertInstallerPackage(ComponentName who, String installerPackage)
throws SecurityException {
@@ -9398,6 +9460,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
"createAndManageUser was called from non-system user");
Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER);
+
final boolean ephemeral = (flags & DevicePolicyManager.MAKE_USER_EPHEMERAL) != 0;
final boolean demo = (flags & DevicePolicyManager.MAKE_USER_DEMO) != 0
&& UserManager.isDeviceInDemoMode(mContext);
@@ -9526,6 +9590,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Objects.requireNonNull(userHandle, "UserHandle is null");
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REMOVE_USER);
return mInjector.binderWithCleanCallingIdentity(() -> {
String restriction = isManagedProfile(userHandle.getIdentifier())
@@ -9559,6 +9624,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SWITCH_USER);
synchronized (getLockObject()) {
long id = mInjector.binderClearCallingIdentity();
@@ -9583,6 +9649,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Objects.requireNonNull(userHandle, "UserHandle is null");
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_START_USER_IN_BACKGROUND);
final int userId = userHandle.getIdentifier();
if (isManagedProfile(userId)) {
@@ -9616,6 +9683,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Objects.requireNonNull(userHandle, "UserHandle is null");
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_STOP_USER);
final int userId = userHandle.getIdentifier();
if (isManagedProfile(userId)) {
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index 7c3b7a67178e..49a41f02c484 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -74,7 +74,14 @@ public class PeopleService extends SystemService {
@Override
public void onStart() {
- publishBinderService(Context.PEOPLE_SERVICE, mService);
+ onStart(/* isForTesting= */ false);
+ }
+
+ @VisibleForTesting
+ protected void onStart(boolean isForTesting) {
+ if (!isForTesting) {
+ publishBinderService(Context.PEOPLE_SERVICE, mService);
+ }
publishLocalService(PeopleServiceInternal.class, new LocalService());
}
diff --git a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
index 9213e1fe5a25..a112b145fdc9 100644
--- a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
@@ -69,7 +69,7 @@ public final class PeopleServiceTest {
when(mCallback.asBinder()).thenReturn(new Binder());
PeopleService service = new PeopleService(mContext);
- service.onStart();
+ service.onStart(/* isForTesting= */ true);
mServiceInternal = LocalServices.getService(PeopleServiceInternal.class);
mLocalService = (PeopleService.LocalService) mServiceInternal;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index dad18ffe4fa7..baee4736b527 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -53,9 +53,13 @@ public class CarrierConfigManager {
public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
/**
- * Extra included in {@link #ACTION_CARRIER_CONFIG_CHANGED} to indicate whether this is a
- * rebroadcast on unlock. Defaults to {@code false} if not specified.
- * @hide
+ * {@link #ACTION_CARRIER_CONFIG_CHANGED} is broadcast once on device bootup and then again when
+ * the device is unlocked. Direct-Boot-aware applications may use the first broadcast as an
+ * early signal that the carrier config has been loaded, but other applications will only
+ * receive the second broadcast, when the device is unlocked.
+ *
+ * This extra is included in {@link #ACTION_CARRIER_CONFIG_CHANGED} to indicate whether this is
+ * a rebroadcast on unlock.
*/
public static final String EXTRA_REBROADCAST_ON_UNLOCK =
"android.telephony.extra.REBROADCAST_ON_UNLOCK";
@@ -2029,8 +2033,16 @@ public class CarrierConfigManager {
"allow_hold_call_during_emergency_bool";
/**
- * Flag indicating whether the carrier supports RCS presence indication for
- * User Capability Exchange (UCE). When presence is supported, the device should use the
+ * Flag indicating whether or not the carrier supports the periodic exchange of phone numbers
+ * in the user's address book with the carrier's presence server in order to retrieve the RCS
+ * capabilities for each contact used in the RCS User Capability Exchange (UCE) procedure. See
+ * RCC.71, section 3 for more information.
+ * <p>
+ * The flag {@link Ims#KEY_ENABLE_PRESENCE_PUBLISH_BOOL} must also be enabled if this flag is
+ * enabled, as sending a periodic SIP PUBLISH with this device's RCS capabilities is a
+ * requirement for capability exchange to begin.
+ * <p>
+ * When presence is supported, the device should use the
* {@link android.provider.ContactsContract.Data#CARRIER_PRESENCE} bit mask and set the
* {@link android.provider.ContactsContract.Data#CARRIER_PRESENCE_VT_CAPABLE} bit to indicate
* whether each contact supports video calling. The UI is made aware that presence is enabled
@@ -3846,12 +3858,27 @@ public class CarrierConfigManager {
public static final String KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL =
KEY_PREFIX + "ims_single_registration_required_bool";
+ /**
+ * A boolean flag specifying whether or not this carrier supports the device notifying the
+ * network of its RCS capabilities using the SIP PUBLISH procedure defined for User
+ * Capability Exchange (UCE). See RCC.71, section 3 for more information.
+ * <p>
+ * If this key's value is set to false, the procedure for RCS contact capability exchange
+ * via SIP SUBSCRIBE/NOTIFY will also be disabled internally, and
+ * {@link #KEY_USE_RCS_PRESENCE_BOOL} must also be set to false to ensure apps do not
+ * improperly think that capability exchange via SIP PUBLISH is enabled.
+ * <p> The default value for this key is {@code false}.
+ */
+ public static final String KEY_ENABLE_PRESENCE_PUBLISH_BOOL =
+ KEY_PREFIX + "enable_presence_publish_bool";
+
private Ims() {}
private static PersistableBundle getDefaults() {
PersistableBundle defaults = new PersistableBundle();
defaults.putInt(KEY_WIFI_OFF_DEFERRING_TIME_MILLIS_INT, 4000);
defaults.putBoolean(KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false);
+ defaults.putBoolean(KEY_ENABLE_PRESENCE_PUBLISH_BOOL, false);
return defaults;
}
}
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 1b51936e873b..aaa68d6f7d57 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -18,6 +18,7 @@ package android.telephony.ims;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
@@ -207,6 +208,42 @@ public final class ImsCallProfile implements Parcelable {
"android.telephony.ims.extra.RETRY_CALL_FAIL_NETWORKTYPE";
/**
+ * Extra for the call composer call priority, either {@link ImsCallProfile#PRIORITY_NORMAL} or
+ * {@link ImsCallProfile#PRIORITY_URGENT}. It can be set via
+ * {@link #setCallExtraInt(String, int)}.
+ *
+ * Reference: RCC.20 Section 2.4.4.2
+ */
+ public static final String EXTRA_PRIORITY = "android.telephony.ims.extra.PRIORITY";
+
+ // TODO(hallliu) remove the reference to the maximum length and update it later.
+ /**
+ * Extra for the call composer call subject, a string of maximum length 60 characters.
+ * It can be set via {@link #setCallExtra(String, String)}.
+ *
+ * Reference: RCC.20 Section 2.4.3.2
+ */
+ public static final String EXTRA_CALL_SUBJECT = "android.telephony.ims.extra.CALL_SUBJECT";
+
+ /**
+ * Extra for the call composer call location, an {@Link android.location.Location} parcelable
+ * class to represent the geolocation as a latitude and longitude pair. It can be set via
+ * {@link #setCallExtraParcelable(String, Parcelable)}.
+ *
+ * Reference: RCC.20 Section 2.4.3.2
+ */
+ public static final String EXTRA_LOCATION = "android.telephony.ims.extra.LOCATION";
+
+ /**
+ * Extra for the call composer picture URL, a String that indicates the URL on the carrier’s
+ * server infrastructure to get the picture. It can be set via
+ * {@link #setCallExtra(String, String)}.
+ *
+ * Reference: RCC.20 Section 2.4.3.2
+ */
+ public static final String EXTRA_PICTURE_URL = "android.telephony.ims.extra.PICTURE_URL";
+
+ /**
* Values for EXTRA_OIR / EXTRA_CNAP
*/
/**
@@ -244,6 +281,21 @@ public final class ImsCallProfile implements Parcelable {
*/
public static final int DIALSTRING_USSD = 2;
+ // Values for EXTRA_PRIORITY
+ /**
+ * Indicates the call composer call priority is normal.
+ *
+ * Reference: RCC.20 Section 2.4.4.2
+ */
+ public static final int PRIORITY_NORMAL = 0;
+
+ /**
+ * Indicates the call composer call priority is urgent.
+ *
+ * Reference: RCC.20 Section 2.4.4.2
+ */
+ public static final int PRIORITY_URGENT = 1;
+
/**
* Call is not restricted on peer side and High Definition media is supported
*/
@@ -588,6 +640,19 @@ public final class ImsCallProfile implements Parcelable {
return mCallExtras.getInt(name, defaultValue);
}
+ /**
+ * Get the call extras (Parcelable), given the extra name.
+ * @param name call extra name
+ * @return the corresponding call extra Parcelable or null if not applicable
+ */
+ @Nullable
+ public <T extends Parcelable> T getCallExtraParcelable(@Nullable String name) {
+ if (mCallExtras != null) {
+ return mCallExtras.getParcelable(name);
+ }
+ return null;
+ }
+
public void setCallExtra(String name, String value) {
if (mCallExtras != null) {
mCallExtras.putString(name, value);
@@ -607,6 +672,17 @@ public final class ImsCallProfile implements Parcelable {
}
/**
+ * Set the call extra value (Parcelable), given the call extra name.
+ * @param name call extra name
+ * @param parcelable call extra value
+ */
+ public void setCallExtraParcelable(@NonNull String name, @NonNull Parcelable parcelable) {
+ if (mCallExtras != null) {
+ mCallExtras.putParcelable(name, parcelable);
+ }
+ }
+
+ /**
* Set the call restrict cause, which provides the reason why a call has been restricted from
* using High Definition media.
*/
diff --git a/wifi/aidl-export/android/net/wifi/CoexUnsafeChannel.aidl b/wifi/aidl-export/android/net/wifi/CoexUnsafeChannel.aidl
new file mode 100644
index 000000000000..cb359e9b2c1b
--- /dev/null
+++ b/wifi/aidl-export/android/net/wifi/CoexUnsafeChannel.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2020, 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.net.wifi;
+
+parcelable CoexUnsafeChannel;
diff --git a/wifi/api/system-current.txt b/wifi/api/system-current.txt
index 5e8fc0774cce..48d9fd453b05 100644
--- a/wifi/api/system-current.txt
+++ b/wifi/api/system-current.txt
@@ -1,6 +1,17 @@
// Signature format: 2.0
package android.net.wifi {
+ public final class CoexUnsafeChannel implements android.os.Parcelable {
+ ctor public CoexUnsafeChannel(int, int);
+ ctor public CoexUnsafeChannel(int, int, int);
+ method public int getBand();
+ method public int getChannel();
+ method public int getPowerCapDbm();
+ method public boolean isPowerCapAvailable();
+ method public void setPowerCapDbm(int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.CoexUnsafeChannel> CREATOR;
+ }
+
public abstract class EasyConnectStatusCallback {
ctor public EasyConnectStatusCallback();
method public void onBootstrapUriGenerated(@NonNull String);
@@ -456,6 +467,8 @@ package android.net.wifi {
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void factoryReset();
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void forget(int, @Nullable android.net.wifi.WifiManager.ActionListener);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.util.Pair<android.net.wifi.WifiConfiguration,java.util.Map<java.lang.Integer,java.util.List<android.net.wifi.ScanResult>>>> getAllMatchingWifiConfigs(@NonNull java.util.List<android.net.wifi.ScanResult>);
+ method @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS) public int getCoexRestrictions();
+ method @NonNull @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS) public java.util.Set<android.net.wifi.CoexUnsafeChannel> getCoexUnsafeChannels();
method @Nullable @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCountryCode();
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public android.net.Network getCurrentNetwork();
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String[] getFactoryMacAddresses();
@@ -476,6 +489,7 @@ package android.net.wifi {
method public boolean isVerboseLoggingEnabled();
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiApEnabled();
method public boolean isWifiScannerSupported();
+ method @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS) public void registerCoexCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.CoexCallback);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerNetworkRequestMatchCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerSoftApCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SoftApCallback);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerTrafficStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.TrafficStateCallback);
@@ -487,6 +501,7 @@ package android.net.wifi {
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveSoftApBackupData();
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void save(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setAutoWakeupEnabled(boolean);
+ method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS) public void setCoexUnsafeChannels(@NonNull java.util.Set<android.net.wifi.CoexUnsafeChannel>, int);
method @RequiresPermission(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE) public void setDeviceMobilityState(int);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setMacRandomizationSettingPasspointEnabled(@NonNull String, boolean);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setPasspointMeteredOverride(@NonNull String, int);
@@ -507,6 +522,7 @@ package android.net.wifi {
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopEasyConnectSession();
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public boolean stopSoftAp();
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void stopTemporarilyDisablingAllNonCarrierMergedWifi();
+ method @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS) public void unregisterCoexCallback(@NonNull android.net.wifi.WifiManager.CoexCallback);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void unregisterNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void unregisterSoftApCallback(@NonNull android.net.wifi.WifiManager.SoftApCallback);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void unregisterTrafficStateCallback(@NonNull android.net.wifi.WifiManager.TrafficStateCallback);
@@ -520,6 +536,9 @@ package android.net.wifi {
field public static final int CHANGE_REASON_ADDED = 0; // 0x0
field public static final int CHANGE_REASON_CONFIG_CHANGE = 2; // 0x2
field public static final int CHANGE_REASON_REMOVED = 1; // 0x1
+ field public static final int COEX_RESTRICTION_SOFTAP = 2; // 0x2
+ field public static final int COEX_RESTRICTION_WIFI_AWARE = 4; // 0x4
+ field public static final int COEX_RESTRICTION_WIFI_DIRECT = 1; // 0x1
field public static final String CONFIGURED_NETWORKS_CHANGED_ACTION = "android.net.wifi.CONFIGURED_NETWORKS_CHANGE";
field public static final int DEVICE_MOBILITY_STATE_HIGH_MVMT = 1; // 0x1
field public static final int DEVICE_MOBILITY_STATE_LOW_MVMT = 2; // 0x2
@@ -575,6 +594,11 @@ package android.net.wifi {
method public void onSuccess();
}
+ public abstract static class WifiManager.CoexCallback {
+ ctor public WifiManager.CoexCallback();
+ method public abstract void onCoexUnsafeChannelsChanged();
+ }
+
public static interface WifiManager.NetworkRequestMatchCallback {
method public default void onAbort();
method public default void onMatch(@NonNull java.util.List<android.net.wifi.ScanResult>);
diff --git a/wifi/java/android/net/wifi/CoexUnsafeChannel.java b/wifi/java/android/net/wifi/CoexUnsafeChannel.java
new file mode 100644
index 000000000000..3f9efa020d05
--- /dev/null
+++ b/wifi/java/android/net/wifi/CoexUnsafeChannel.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2020 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.net.wifi;
+
+import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
+import static android.net.wifi.WifiScanner.WIFI_BAND_5_GHZ;
+import static android.net.wifi.WifiScanner.WIFI_BAND_6_GHZ;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Data structure class representing a Wi-Fi channel that would cause interference to/receive
+ * interference from the active cellular channels and should be avoided.
+ *
+ * If {@link #isPowerCapAvailable()} is {@code true}, then a valid power cap value is available
+ * through {@link #getPowerCapDbm()} to be used if this channel cannot be avoided. If {@code false},
+ * then {@link #getPowerCapDbm()} throws an IllegalStateException and the channel will not need to
+ * cap its power.
+ *
+ * @hide
+ */
+@SystemApi
+public final class CoexUnsafeChannel implements Parcelable {
+ private @WifiAnnotations.WifiBandBasic int mBand;
+ private int mChannel;
+ private boolean mIsPowerCapAvailable = false;
+ private int mPowerCapDbm;
+
+ /**
+ * Constructor for a CoexUnsafeChannel with no power cap specified.
+ * @param band One of {@link WifiAnnotations.WifiBandBasic}
+ * @param channel Channel number
+ */
+ public CoexUnsafeChannel(@WifiAnnotations.WifiBandBasic int band, int channel) {
+ mBand = band;
+ mChannel = channel;
+ }
+
+ /**
+ * Constructor for a CoexUnsafeChannel with power cap specified.
+ * @param band One of {@link WifiAnnotations.WifiBandBasic}
+ * @param channel Channel number
+ * @param powerCapDbm Power cap in dBm
+ */
+ public CoexUnsafeChannel(@WifiAnnotations.WifiBandBasic int band, int channel,
+ int powerCapDbm) {
+ mBand = band;
+ mChannel = channel;
+ setPowerCapDbm(powerCapDbm);
+ }
+
+ /** Returns the Wi-Fi band of this channel as one of {@link WifiAnnotations.WifiBandBasic} */
+ public @WifiAnnotations.WifiBandBasic int getBand() {
+ return mBand;
+ }
+
+ /** Returns the channel number of this channel. */
+ public int getChannel() {
+ return mChannel;
+ }
+
+ /** Returns {@code true} if {@link #getPowerCapDbm()} is a valid value, else {@code false} */
+ public boolean isPowerCapAvailable() {
+ return mIsPowerCapAvailable;
+ }
+
+ /**
+ * Returns the power cap of this channel in dBm. Throws IllegalStateException if
+ * {@link #isPowerCapAvailable()} is {@code false}.
+ */
+ public int getPowerCapDbm() {
+ if (!mIsPowerCapAvailable) {
+ throw new IllegalStateException("getPowerCapDbm called but power cap is unavailable");
+ }
+ return mPowerCapDbm;
+ }
+
+ /** Set the power cap of this channel. */
+ public void setPowerCapDbm(int powerCapDbm) {
+ mIsPowerCapAvailable = true;
+ mPowerCapDbm = powerCapDbm;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ CoexUnsafeChannel that = (CoexUnsafeChannel) o;
+ return mBand == that.mBand
+ && mChannel == that.mChannel
+ && mIsPowerCapAvailable == that.mIsPowerCapAvailable
+ && mPowerCapDbm == that.mPowerCapDbm;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mBand, mChannel, mIsPowerCapAvailable, mPowerCapDbm);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sj = new StringBuilder("CoexUnsafeChannel{");
+ sj.append(mChannel);
+ sj.append(", ");
+ if (mBand == WIFI_BAND_24_GHZ) {
+ sj.append("2.4GHz");
+ } else if (mBand == WIFI_BAND_5_GHZ) {
+ sj.append("5GHz");
+ } else if (mBand == WIFI_BAND_6_GHZ) {
+ sj.append("6GHz");
+ } else {
+ sj.append("UNKNOWN BAND");
+ }
+ if (mIsPowerCapAvailable) {
+ sj.append(", ").append(mPowerCapDbm).append("dBm");
+ }
+ sj.append('}');
+ return sj.toString();
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mBand);
+ dest.writeInt(mChannel);
+ dest.writeBoolean(mIsPowerCapAvailable);
+ if (mIsPowerCapAvailable) {
+ dest.writeInt(mPowerCapDbm);
+ }
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @NonNull Creator<CoexUnsafeChannel> CREATOR =
+ new Creator<CoexUnsafeChannel>() {
+ public CoexUnsafeChannel createFromParcel(Parcel in) {
+ final int band = in.readInt();
+ final int channel = in.readInt();
+ final boolean isPowerCapAvailable = in.readBoolean();
+ if (isPowerCapAvailable) {
+ final int powerCapDbm = in.readInt();
+ return new CoexUnsafeChannel(band, channel, powerCapDbm);
+ }
+ return new CoexUnsafeChannel(band, channel);
+ }
+
+ public CoexUnsafeChannel[] newArray(int size) {
+ return new CoexUnsafeChannel[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/ICoexCallback.aidl b/wifi/java/android/net/wifi/ICoexCallback.aidl
new file mode 100644
index 000000000000..89e4c4b93013
--- /dev/null
+++ b/wifi/java/android/net/wifi/ICoexCallback.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 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.net.wifi;
+
+/**
+ * Interface for Wi-Fi/cellular coex callback.
+ * @hide
+ */
+oneway interface ICoexCallback
+{
+ void onCoexUnsafeChannelsChanged();
+}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 0b3c0575dabd..6dee751f5894 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -24,7 +24,9 @@ import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.DhcpInfo;
import android.net.Network;
+import android.net.wifi.CoexUnsafeChannel;
import android.net.wifi.IActionListener;
+import android.net.wifi.ICoexCallback;
import android.net.wifi.IDppCallback;
import android.net.wifi.ILocalOnlyHotspotCallback;
import android.net.wifi.INetworkRequestMatchCallback;
@@ -144,6 +146,16 @@ interface IWifiManager
void updateInterfaceIpState(String ifaceName, int mode);
+ void setCoexUnsafeChannels(in List<CoexUnsafeChannel> unsafeChannels, int mandatoryRestrictions);
+
+ List<CoexUnsafeChannel> getCoexUnsafeChannels();
+
+ int getCoexRestrictions();
+
+ void registerCoexCallback(in ICoexCallback callback);
+
+ void unregisterCoexCallback(in ICoexCallback callback);
+
boolean startSoftAp(in WifiConfiguration wifiConfig, String packageName);
boolean startTetheredHotspot(in SoftApConfiguration softApConfig, String packageName);
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index d089e1a86a68..b8fa1e18ed28 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -72,6 +72,7 @@ import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -3110,6 +3111,238 @@ public class WifiManager {
}
}
+ /* Wi-Fi/Cellular Coex */
+
+ /**
+ * Mandatory coex restriction flag for Wi-Fi Direct.
+ *
+ * @see #setCoexUnsafeChannels(Set, int)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int COEX_RESTRICTION_WIFI_DIRECT = 0x1 << 0;
+
+ /**
+ * Mandatory coex restriction flag for SoftAP
+ *
+ * @see #setCoexUnsafeChannels(Set, int)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int COEX_RESTRICTION_SOFTAP = 0x1 << 1;
+
+ /**
+ * Mandatory coex restriction flag for Wi-Fi Aware.
+ *
+ * @see #setCoexUnsafeChannels(Set, int)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int COEX_RESTRICTION_WIFI_AWARE = 0x1 << 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = {"COEX_RESTRICTION_"}, value = {
+ COEX_RESTRICTION_WIFI_DIRECT,
+ COEX_RESTRICTION_SOFTAP,
+ COEX_RESTRICTION_WIFI_AWARE
+ })
+ public @interface CoexRestriction {}
+
+ /**
+ * Specify the set of {@link CoexUnsafeChannel} to propagate through the framework for
+ * Wi-Fi/Cellular coex channel avoidance if the default algorithm is disabled via overlay
+ * (i.e. config_wifiCoexDefaultAlgorithmEnabled = false). Otherwise do nothing.
+ *
+ * @param unsafeChannels Set of {@link CoexUnsafeChannel} to avoid.
+ * @param restrictions Bitmap of {@link CoexRestriction} specifying the mandatory restricted
+ * uses of the specified channels. If any restrictions are set, then the
+ * supplied CoexUnsafeChannels will be completely avoided for the
+ * specified modes, rather than be avoided with best effort.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS)
+ public void setCoexUnsafeChannels(@NonNull Set<CoexUnsafeChannel> unsafeChannels,
+ int restrictions) {
+ if (unsafeChannels == null) {
+ throw new IllegalArgumentException("unsafeChannels must not be null");
+ }
+ try {
+ mService.setCoexUnsafeChannels(new ArrayList<>(unsafeChannels), restrictions);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the set of current {@link CoexUnsafeChannel} being used for Wi-Fi/Cellular coex
+ * channel avoidance.
+ *
+ * This returns the set calculated by the default algorithm if
+ * config_wifiCoexDefaultAlgorithmEnabled is {@code true}. Otherwise, returns the set supplied
+ * in {@link #setCoexUnsafeChannels(Set, int)}.
+ *
+ * If any {@link CoexRestriction} flags are set in {@link #getCoexRestrictions()}, then the
+ * CoexUnsafeChannels should be totally avoided (i.e. not best effort) for the Wi-Fi modes
+ * specified by the flags.
+ *
+ * @return Set of current CoexUnsafeChannels.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS)
+ public Set<CoexUnsafeChannel> getCoexUnsafeChannels() {
+ try {
+ return new HashSet<>(mService.getCoexUnsafeChannels());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the current coex restrictions being used for Wi-Fi/Cellular coex
+ * channel avoidance.
+ *
+ * This returns the restrictions calculated by the default algorithm if
+ * config_wifiCoexDefaultAlgorithmEnabled is {@code true}. Otherwise, returns the value supplied
+ * in {@link #setCoexUnsafeChannels(Set, int)}.
+ *
+ * @return int containing a bitwise-OR combination of {@link CoexRestriction}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS)
+ public int getCoexRestrictions() {
+ try {
+ return mService.getCoexRestrictions();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers a CoexCallback to listen on the current CoexUnsafeChannels and restrictions being
+ * used for Wi-Fi/cellular coex channel avoidance.
+ * @param executor Executor to execute listener callback on
+ * @param callback CoexCallback to register
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS)
+ public void registerCoexCallback(
+ @NonNull @CallbackExecutor Executor executor, @NonNull CoexCallback callback) {
+ if (executor == null) throw new IllegalArgumentException("executor must not be null");
+ if (callback == null) throw new IllegalArgumentException("callback must not be null");
+ CoexCallback.CoexCallbackProxy proxy = callback.getProxy();
+ proxy.initProxy(executor, callback);
+ try {
+ mService.registerCoexCallback(proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregisters a CoexCallback from listening on the current CoexUnsafeChannels and restrictions
+ * being used for Wi-Fi/cellular coex channel avoidance.
+ * @param callback CoexCallback to unregister
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS)
+ public void unregisterCoexCallback(@NonNull CoexCallback callback) {
+ if (callback == null) throw new IllegalArgumentException("callback must not be null");
+ CoexCallback.CoexCallbackProxy proxy = callback.getProxy();
+ try {
+ mService.unregisterCoexCallback(proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } finally {
+ proxy.cleanUpProxy();
+ }
+ }
+
+ /**
+ * Abstract callback class for applications to receive updates about current CoexUnsafeChannels
+ * for Wi-Fi/Cellular coex channel avoidance.
+ *
+ * @hide
+ */
+ @SystemApi
+ public abstract static class CoexCallback {
+ private final CoexCallbackProxy mCoexCallbackProxy;
+
+ public CoexCallback() {
+ mCoexCallbackProxy = new CoexCallbackProxy();
+ }
+
+ /*package*/ @NonNull
+ CoexCallbackProxy getProxy() {
+ return mCoexCallbackProxy;
+ }
+
+ /**
+ * Indicates that the current CoexUnsafeChannels or restrictions have changed.
+ * Clients should call {@link #getCoexUnsafeChannels()} and {@link #getCoexRestrictions()}
+ * to get the updated values.
+ */
+ public abstract void onCoexUnsafeChannelsChanged();
+
+ /**
+ * Callback proxy for CoexCallback objects.
+ */
+ private static class CoexCallbackProxy extends ICoexCallback.Stub {
+ private final Object mLock = new Object();
+ @Nullable @GuardedBy("mLock") private Executor mExecutor;
+ @Nullable @GuardedBy("mLock") private CoexCallback mCallback;
+
+ CoexCallbackProxy() {
+ mExecutor = null;
+ mCallback = null;
+ }
+
+ /*package*/ void initProxy(@NonNull Executor executor,
+ @NonNull CoexCallback callback) {
+ synchronized (mLock) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+ }
+
+ /*package*/ void cleanUpProxy() {
+ synchronized (mLock) {
+ mExecutor = null;
+ mCallback = null;
+ }
+ }
+
+ @Override
+ public void onCoexUnsafeChannelsChanged() {
+ Executor executor;
+ CoexCallback callback;
+ synchronized (mLock) {
+ executor = mExecutor;
+ callback = mCallback;
+ }
+ if (executor == null || callback == null) {
+ return;
+ }
+ Binder.clearCallingIdentity();
+ executor.execute(callback::onCoexUnsafeChannelsChanged);
+ }
+ }
+ }
+
/**
* Start Soft AP (hotspot) mode for tethering purposes with the specified configuration.
* Note that starting Soft AP mode may disable station mode operation if the device does not
@@ -5991,7 +6224,6 @@ public class WifiManager {
executor.execute(callback::onScanResultsAvailable);
}
}
-
}
/**
diff --git a/wifi/tests/src/android/net/wifi/CoexUnsafeChannelTest.java b/wifi/tests/src/android/net/wifi/CoexUnsafeChannelTest.java
new file mode 100644
index 000000000000..320f25e715fe
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/CoexUnsafeChannelTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 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.net.wifi;
+
+import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.CoexUnsafeChannel}.
+ */
+@SmallTest
+public class CoexUnsafeChannelTest {
+ /**
+ * Verifies {@link CoexUnsafeChannel#isPowerCapAvailable()} returns false if no cap is set.
+ */
+ @Test
+ public void testIsPowerCapAvailable_noPowerCap_returnsFalse() {
+ CoexUnsafeChannel unsafeChannel = new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6);
+
+ assertThat(unsafeChannel.isPowerCapAvailable()).isFalse();
+ }
+
+ /**
+ * Verifies {@link CoexUnsafeChannel#isPowerCapAvailable()} returns true if a cap is set, and
+ * {@link CoexUnsafeChannel#getPowerCapDbm()} returns the set value.
+ */
+ @Test
+ public void testIsPowerCapAvailable_powerCapSet_returnsTrue() {
+ final int powerCapDbm = -50;
+ CoexUnsafeChannel unsafeChannel = new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6);
+
+ unsafeChannel.setPowerCapDbm(powerCapDbm);
+
+ assertThat(unsafeChannel.isPowerCapAvailable()).isTrue();
+ assertThat(unsafeChannel.getPowerCapDbm()).isEqualTo(powerCapDbm);
+ }
+
+ /**
+ * Verifies {@link CoexUnsafeChannel#getPowerCapDbm()} throws an IllegalStateException if
+ * {@link CoexUnsafeChannel#isPowerCapAvailable()} is {@code false}.
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testGetPowerCap_powerCapUnavailable_throwsException() {
+ CoexUnsafeChannel unsafeChannel = new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6);
+
+ unsafeChannel.getPowerCapDbm();
+ }
+
+ /**
+ * Verify parcel read/write for CoexUnsafeChannel with or without power cap.
+ */
+ @Test
+ public void testParcelReadWrite_withOrWithoutCap_readEqualsWritten() throws Exception {
+ CoexUnsafeChannel writeUnsafeChannelNoCap =
+ new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6);
+ CoexUnsafeChannel writeUnsafeChannelCapped =
+ new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6, -50);
+
+ CoexUnsafeChannel readUnsafeChannelNoCap = parcelReadWrite(writeUnsafeChannelNoCap);
+ CoexUnsafeChannel readUnsafeChannelCapped = parcelReadWrite(writeUnsafeChannelCapped);
+
+ assertThat(writeUnsafeChannelNoCap).isEqualTo(readUnsafeChannelNoCap);
+ assertThat(writeUnsafeChannelCapped).isEqualTo(readUnsafeChannelCapped);
+ }
+
+ /**
+ * Write the provided {@link CoexUnsafeChannel} to a parcel and deserialize it.
+ */
+ private static CoexUnsafeChannel parcelReadWrite(CoexUnsafeChannel writeResult)
+ throws Exception {
+ Parcel parcel = Parcel.obtain();
+ writeResult.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ return CoexUnsafeChannel.CREATOR.createFromParcel(parcel);
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index aefebbcec607..39f6f57b05b3 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -19,6 +19,9 @@ package android.net.wifi;
import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_METERED;
import static android.net.wifi.WifiManager.ActionListener;
import static android.net.wifi.WifiManager.BUSY;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_SOFTAP;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_AWARE;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_DIRECT;
import static android.net.wifi.WifiManager.ERROR;
import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_GENERIC;
import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE;
@@ -43,6 +46,7 @@ import static android.net.wifi.WifiManager.WIFI_FEATURE_SCANNER;
import static android.net.wifi.WifiManager.WIFI_FEATURE_WPA3_SAE;
import static android.net.wifi.WifiManager.WIFI_FEATURE_WPA3_SUITE_B;
import static android.net.wifi.WifiManager.WpsCallback;
+import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -74,6 +78,7 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.net.DhcpInfo;
import android.net.MacAddress;
+import android.net.wifi.WifiManager.CoexCallback;
import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
import android.net.wifi.WifiManager.LocalOnlyHotspotObserver;
import android.net.wifi.WifiManager.LocalOnlyHotspotReservation;
@@ -108,9 +113,11 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -151,6 +158,7 @@ public class WifiManagerTest {
private WifiManager mWifiManager;
private WifiNetworkSuggestion mWifiNetworkSuggestion;
private ScanResultsCallback mScanResultsCallback;
+ private CoexCallback mCoexCallback;
private WifiActivityEnergyInfo mWifiActivityEnergyInfo;
/**
@@ -214,10 +222,149 @@ public class WifiManagerTest {
mRunnable.run();
}
};
+ mCoexCallback = new CoexCallback() {
+ @Override
+ public void onCoexUnsafeChannelsChanged() {
+ mRunnable.run();
+ }
+ };
mWifiActivityEnergyInfo = new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0);
}
/**
+ * Check the call to setCoexUnsafeChannels calls WifiServiceImpl to setCoexUnsafeChannels with
+ * the provided CoexUnsafeChannels and restrictions bitmask.
+ */
+ @Test
+ public void testSetCoexUnsafeChannelsGoesToWifiServiceImpl() throws Exception {
+ Set<CoexUnsafeChannel> unsafeChannels = new HashSet<>();
+ int restrictions = COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP
+ | COEX_RESTRICTION_WIFI_AWARE;
+
+ mWifiManager.setCoexUnsafeChannels(unsafeChannels, restrictions);
+
+ verify(mWifiService).setCoexUnsafeChannels(new ArrayList<>(unsafeChannels), restrictions);
+ }
+
+ /**
+ * Verify an IllegalArgumentException if passed a null value for unsafeChannels.
+ */
+ @Test
+ public void testSetCoexUnsafeChannelsThrowsIllegalArgumentExceptionOnNullUnsafeChannels() {
+ try {
+ mWifiManager.setCoexUnsafeChannels(null, 0);
+ fail("expected IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ /**
+ * Check the call to getCoexUnsafeChannels calls WifiServiceImpl to return the values from
+ * getCoexUnsafeChannels.
+ */
+ @Test
+ public void testGetCoexUnsafeChannelsGoesToWifiServiceImpl() throws Exception {
+ Set<CoexUnsafeChannel> unsafeChannels = new HashSet<>();
+ unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6));
+ when(mWifiService.getCoexUnsafeChannels()).thenReturn(new ArrayList<>(unsafeChannels));
+
+ assertEquals(mWifiManager.getCoexUnsafeChannels(), unsafeChannels);
+ }
+
+ /**
+ * Verify call to getCoexRestrictions calls WifiServiceImpl to return the value from
+ * getCoexRestrictions.
+ */
+ @Test
+ public void testGetCoexRestrictionsGoesToWifiServiceImpl() throws Exception {
+ int restrictions = COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP
+ | COEX_RESTRICTION_WIFI_AWARE;
+ when(mWifiService.getCoexRestrictions()).thenReturn(restrictions);
+
+ assertEquals(mWifiService.getCoexRestrictions(), restrictions);
+ }
+
+
+ /**
+ * Verify an IllegalArgumentException is thrown if callback is not provided.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testRegisterCoexCallbackWithNullCallback() throws Exception {
+ mWifiManager.registerCoexCallback(mExecutor, null);
+ }
+
+ /**
+ * Verify an IllegalArgumentException is thrown if executor is not provided.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testRegisterCoexCallbackWithNullExecutor() throws Exception {
+ mWifiManager.registerCoexCallback(null, mCoexCallback);
+ }
+
+ /**
+ * Verify client provided callback is being called to the right callback.
+ */
+ @Test
+ public void testAddCoexCallbackAndReceiveEvent() throws Exception {
+ ArgumentCaptor<ICoexCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ICoexCallback.Stub.class);
+ mWifiManager.registerCoexCallback(new SynchronousExecutor(), mCoexCallback);
+ verify(mWifiService).registerCoexCallback(callbackCaptor.capture());
+ callbackCaptor.getValue().onCoexUnsafeChannelsChanged();
+ verify(mRunnable).run();
+ }
+
+ /**
+ * Verify client provided callback is being called to the right executor.
+ */
+ @Test
+ public void testRegisterCoexCallbackWithTheTargetExecutor() throws Exception {
+ ArgumentCaptor<ICoexCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ICoexCallback.Stub.class);
+ mWifiManager.registerCoexCallback(mExecutor, mCoexCallback);
+ verify(mWifiService).registerCoexCallback(callbackCaptor.capture());
+ mWifiManager.registerCoexCallback(mAnotherExecutor, mCoexCallback);
+ callbackCaptor.getValue().onCoexUnsafeChannelsChanged();
+ verify(mExecutor, never()).execute(any(Runnable.class));
+ verify(mAnotherExecutor).execute(any(Runnable.class));
+ }
+
+ /**
+ * Verify client register unregister then register again, to ensure callback still works.
+ */
+ @Test
+ public void testRegisterUnregisterThenRegisterAgainWithCoexCallback() throws Exception {
+ ArgumentCaptor<ICoexCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ICoexCallback.Stub.class);
+ mWifiManager.registerCoexCallback(new SynchronousExecutor(), mCoexCallback);
+ verify(mWifiService).registerCoexCallback(callbackCaptor.capture());
+ mWifiManager.unregisterCoexCallback(mCoexCallback);
+ callbackCaptor.getValue().onCoexUnsafeChannelsChanged();
+ verify(mRunnable, never()).run();
+ mWifiManager.registerCoexCallback(new SynchronousExecutor(), mCoexCallback);
+ callbackCaptor.getValue().onCoexUnsafeChannelsChanged();
+ verify(mRunnable).run();
+ }
+
+ /**
+ * Verify client unregisterCoexCallback.
+ */
+ @Test
+ public void testUnregisterCoexCallback() throws Exception {
+ mWifiManager.unregisterCoexCallback(mCoexCallback);
+ verify(mWifiService).unregisterCoexCallback(any());
+ }
+
+ /**
+ * Verify client unregisterCoexCallback with null callback will cause an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testUnregisterCoexCallbackWithNullCallback() throws Exception {
+ mWifiManager.unregisterCoexCallback(null);
+ }
+
+
+ /**
* Check the call to startSoftAp calls WifiService to startSoftAp with the provided
* WifiConfiguration. Verify that the return value is propagated to the caller.
*/