Merge tag 'android-security-11.0.0_r54' into int/11/fp3
Android security 11.0.0 release 54
* tag 'android-security-11.0.0_r54':
[RESTRICT AUTOMERGE] Add hide-non-system-overlay flag for HarmfulAppWarningActivity
BG-FGS-start while-in-use permission restriction improvement.
Restrict AdbManager broadcasts to apps with MANAGE_DEBUGGING permission.
Prevent apps from creating blocked channel groups
Add ALLOW_SLIPPERY_TOUCHES permission
Handle onNullBinding
Only allow trusted overlays to specify FLAG_SLIPPERY
Check group channels for FGSes
[RESTRICT AUTOMERGE] Fix the inconsistency of protection level
Don't abandon child sessions (1/n)
Don't crash if default supervision profile owner is not set
DO NOT MERGE Re-implement reading/writing Throwables from/to Parcel, without Parcel private APIs.
[DO NOT MERGE] Controls - Do not recreate intent
RESTRICT AUTOMERGE Remove line of code that was mistakently left in.
Revert "Revert "[pm] remove old stage dirs on low storage""
Update deletion conditions for a package's UsageStats.
Allow forcing status bar state changes and do so when the screen turns off.
Prevent apps from spamming addAccountExplicitly. See comment here for the discussion on solution https://b.corp.google.com/issues/169762606#comment14
Change-Id: I1263e7c1c8d167aca05ffd63b347d200a088d700
diff --git a/core/java/android/accounts/Account.java b/core/java/android/accounts/Account.java
index 9a18880..965b6e0 100644
--- a/core/java/android/accounts/Account.java
+++ b/core/java/android/accounts/Account.java
@@ -30,6 +30,7 @@
import com.android.internal.annotations.GuardedBy;
+import java.util.Objects;
import java.util.Set;
/**
@@ -85,6 +86,12 @@
if (TextUtils.isEmpty(type)) {
throw new IllegalArgumentException("the type must not be empty: " + type);
}
+ if (name.length() > 200) {
+ throw new IllegalArgumentException("account name is longer than 200 characters");
+ }
+ if (type.length() > 200) {
+ throw new IllegalArgumentException("account type is longer than 200 characters");
+ }
this.name = name;
this.type = type;
this.accessId = accessId;
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index d24694f..60cb563 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -16,6 +16,7 @@
package android.app.admin;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
@@ -77,6 +78,13 @@
OnCrossProfileWidgetProvidersChangeListener listener);
/**
+ * @param userHandle the handle of the user whose profile owner is being fetched.
+ * @return the configured supervision app if it exists and is the device owner or policy owner.
+ */
+ public abstract @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent(
+ @NonNull UserHandle userHandle);
+
+ /**
* Checks if an app with given uid is an active device admin of its user and has the policy
* specified.
*
diff --git a/core/java/android/debug/AdbManager.java b/core/java/android/debug/AdbManager.java
index 7714dd8..243f801 100644
--- a/core/java/android/debug/AdbManager.java
+++ b/core/java/android/debug/AdbManager.java
@@ -38,6 +38,7 @@
*
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.MANAGE_DEBUGGING)
public static final String WIRELESS_DEBUG_STATE_CHANGED_ACTION =
"com.android.server.adb.WIRELESS_DEBUG_STATUS";
@@ -46,6 +47,7 @@
*
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.MANAGE_DEBUGGING)
public static final String WIRELESS_DEBUG_PAIRED_DEVICES_ACTION =
"com.android.server.adb.WIRELESS_DEBUG_PAIRED_DEVICES";
@@ -59,6 +61,7 @@
*
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.MANAGE_DEBUGGING)
public static final String WIRELESS_DEBUG_PAIRING_RESULT_ACTION =
"com.android.server.adb.WIRELESS_DEBUG_PAIRING_RESULT";
diff --git a/core/java/com/android/internal/app/HarmfulAppWarningActivity.java b/core/java/com/android/internal/app/HarmfulAppWarningActivity.java
index ce2d229..33209e1 100644
--- a/core/java/com/android/internal/app/HarmfulAppWarningActivity.java
+++ b/core/java/com/android/internal/app/HarmfulAppWarningActivity.java
@@ -16,6 +16,8 @@
package com.android.internal.app;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -27,6 +29,7 @@
import android.util.Log;
import android.view.View;
import android.widget.TextView;
+
import com.android.internal.R;
/**
@@ -48,6 +51,7 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
final Intent intent = getIntent();
mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
mTarget = intent.getParcelableExtra(Intent.EXTRA_INTENT);
diff --git a/core/java/com/android/internal/infra/AndroidFuture.java b/core/java/com/android/internal/infra/AndroidFuture.java
index 9f15d89..84391c1 100644
--- a/core/java/com/android/internal/infra/AndroidFuture.java
+++ b/core/java/com/android/internal/infra/AndroidFuture.java
@@ -16,23 +16,21 @@
package com.android.internal.infra;
-import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
-
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
-import android.os.Message;
+import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
-import android.util.ExceptionUtils;
+import android.util.EventLog;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
-import com.android.internal.util.function.pooled.PooledLambda;
+import java.lang.reflect.Constructor;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
@@ -75,14 +73,16 @@
private static final boolean DEBUG = false;
private static final String LOG_TAG = AndroidFuture.class.getSimpleName();
+ private static final Executor DIRECT_EXECUTOR = Runnable::run;
private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
+ private static @Nullable Handler sMainHandler;
private final @NonNull Object mLock = new Object();
@GuardedBy("mLock")
private @Nullable BiConsumer<? super T, ? super Throwable> mListener;
@GuardedBy("mLock")
private @Nullable Executor mListenerExecutor = DIRECT_EXECUTOR;
- private @NonNull Handler mTimeoutHandler = Handler.getMain();
+ private @NonNull Handler mTimeoutHandler = getMainHandler();
private final @Nullable IAndroidFuture mRemoteOrigin;
public AndroidFuture() {
@@ -96,7 +96,7 @@
// Done
if (in.readBoolean()) {
// Failed
- completeExceptionally(unparcelException(in));
+ completeExceptionally(readThrowable(in));
} else {
// Success
complete((T) in.readValue(null));
@@ -108,6 +108,15 @@
}
}
+ @NonNull
+ private static Handler getMainHandler() {
+ // This isn't thread-safe but we are okay with it.
+ if (sMainHandler == null) {
+ sMainHandler = new Handler(Looper.getMainLooper());
+ }
+ return sMainHandler;
+ }
+
/**
* Create a completed future with the given value.
*
@@ -236,9 +245,7 @@
if (mListenerExecutor == DIRECT_EXECUTOR) {
callListener(listener, res, err);
} else {
- mListenerExecutor.execute(PooledLambda
- .obtainRunnable(AndroidFuture::callListener, listener, res, err)
- .recycleOnUse());
+ mListenerExecutor.execute(() -> callListener(listener, res, err));
}
}
@@ -260,7 +267,8 @@
} else {
// listener exception-case threw
// give up on listener but preserve the original exception when throwing up
- throw ExceptionUtils.appendCause(t, err);
+ t.addSuppressed(err);
+ throw t;
}
}
} catch (Throwable t2) {
@@ -272,9 +280,7 @@
/** @inheritDoc */
//@Override //TODO uncomment once java 9 APIs are exposed to frameworks
public AndroidFuture<T> orTimeout(long timeout, @NonNull TimeUnit unit) {
- Message msg = PooledLambda.obtainMessage(AndroidFuture::triggerTimeout, this);
- msg.obj = this;
- mTimeoutHandler.sendMessageDelayed(msg, unit.toMillis(timeout));
+ mTimeoutHandler.postDelayed(this::triggerTimeout, this, unit.toMillis(timeout));
return this;
}
@@ -507,7 +513,7 @@
result = get();
} catch (Throwable t) {
dest.writeBoolean(true);
- parcelException(dest, unwrapExecutionException(t));
+ writeThrowable(dest, unwrapExecutionException(t));
return;
}
dest.writeBoolean(false);
@@ -545,45 +551,75 @@
* Alternative to {@link Parcel#writeException} that stores the stack trace, in a
* way consistent with the binder IPC exception propagation behavior.
*/
- private static void parcelException(Parcel p, @Nullable Throwable t) {
- p.writeBoolean(t == null);
- if (t == null) {
+ private static void writeThrowable(@NonNull Parcel parcel, @Nullable Throwable throwable) {
+ boolean hasThrowable = throwable != null;
+ parcel.writeBoolean(hasThrowable);
+ if (!hasThrowable) {
return;
}
- p.writeInt(Parcel.getExceptionCode(t));
- p.writeString(t.getClass().getName());
- p.writeString(t.getMessage());
- p.writeStackTrace(t);
- parcelException(p, t.getCause());
+ boolean isFrameworkParcelable = throwable instanceof Parcelable
+ && throwable.getClass().getClassLoader() == Parcelable.class.getClassLoader();
+ parcel.writeBoolean(isFrameworkParcelable);
+ if (isFrameworkParcelable) {
+ parcel.writeParcelable((Parcelable) throwable,
+ Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ return;
+ }
+
+ parcel.writeString(throwable.getClass().getName());
+ parcel.writeString(throwable.getMessage());
+ StackTraceElement[] stackTrace = throwable.getStackTrace();
+ StringBuilder stackTraceBuilder = new StringBuilder();
+ int truncatedStackTraceLength = Math.min(stackTrace != null ? stackTrace.length : 0, 5);
+ for (int i = 0; i < truncatedStackTraceLength; i++) {
+ if (i > 0) {
+ stackTraceBuilder.append('\n');
+ }
+ stackTraceBuilder.append("\tat ").append(stackTrace[i]);
+ }
+ parcel.writeString(stackTraceBuilder.toString());
+ writeThrowable(parcel, throwable.getCause());
}
/**
- * @see #parcelException
+ * @see #writeThrowable
*/
- private static @Nullable Throwable unparcelException(Parcel p) {
- if (p.readBoolean()) {
+ private static @Nullable Throwable readThrowable(@NonNull Parcel parcel) {
+ final boolean hasThrowable = parcel.readBoolean();
+ if (!hasThrowable) {
return null;
}
- int exCode = p.readInt();
- String cls = p.readString();
- String msg = p.readString();
- String stackTrace = p.readInt() > 0 ? p.readString() : "\t<stack trace unavailable>";
- msg += "\n" + stackTrace;
-
- Exception ex = p.createExceptionOrNull(exCode, msg);
- if (ex == null) {
- ex = new RuntimeException(cls + ": " + msg);
+ boolean isFrameworkParcelable = parcel.readBoolean();
+ if (isFrameworkParcelable) {
+ return parcel.readParcelable(Parcelable.class.getClassLoader());
}
- ex.setStackTrace(EMPTY_STACK_TRACE);
- Throwable cause = unparcelException(p);
+ String className = parcel.readString();
+ String message = parcel.readString();
+ String stackTrace = parcel.readString();
+ String messageWithStackTrace = message + '\n' + stackTrace;
+ Throwable throwable;
+ try {
+ Class<?> clazz = Class.forName(className, true, Parcelable.class.getClassLoader());
+ if (Throwable.class.isAssignableFrom(clazz)) {
+ Constructor<?> constructor = clazz.getConstructor(String.class);
+ throwable = (Throwable) constructor.newInstance(messageWithStackTrace);
+ } else {
+ android.util.EventLog.writeEvent(0x534e4554, "186530450", -1, "");
+ throwable = new RuntimeException(className + ": " + messageWithStackTrace);
+ }
+ } catch (Throwable t) {
+ throwable = new RuntimeException(className + ": " + messageWithStackTrace);
+ throwable.addSuppressed(t);
+ }
+ throwable.setStackTrace(EMPTY_STACK_TRACE);
+ Throwable cause = readThrowable(parcel);
if (cause != null) {
- ex.initCause(ex);
+ throwable.initCause(cause);
}
-
- return ex;
+ return throwable;
}
@Override
diff --git a/core/java/com/android/internal/infra/ServiceConnector.java b/core/java/com/android/internal/infra/ServiceConnector.java
index 167d128..8986938 100644
--- a/core/java/com/android/internal/infra/ServiceConnector.java
+++ b/core/java/com/android/internal/infra/ServiceConnector.java
@@ -26,14 +26,11 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.IInterface;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.DebugUtils;
import android.util.Log;
-import com.android.internal.util.function.pooled.PooledLambda;
-
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -47,7 +44,6 @@
import java.util.function.BiConsumer;
import java.util.function.Function;
-
/**
* Takes care of managing a {@link ServiceConnection} and auto-disconnecting from the service upon
* a certain timeout.
@@ -220,6 +216,7 @@
private final @NonNull Queue<Job<I, ?>> mQueue = this;
private final @NonNull List<CompletionAwareJob<I, ?>> mUnfinishedJobs = new ArrayList<>();
+ private final @NonNull Handler mMainHandler = new Handler(Looper.getMainLooper());
private final @NonNull ServiceConnection mServiceConnection = this;
private final @NonNull Runnable mTimeoutDisconnect = this;
@@ -250,9 +247,8 @@
* {@link IInterface}.
* Typically this is {@code IMyInterface.Stub::asInterface}
*/
- public Impl(@NonNull Context context, @NonNull Intent intent,
- @Context.BindServiceFlags int bindingFlags, @UserIdInt int userId,
- @Nullable Function<IBinder, I> binderAsInterface) {
+ public Impl(@NonNull Context context, @NonNull Intent intent, int bindingFlags,
+ @UserIdInt int userId, @Nullable Function<IBinder, I> binderAsInterface) {
mContext = context;
mIntent = intent;
mBindingFlags = bindingFlags;
@@ -264,7 +260,7 @@
* {@link Handler} on which {@link Job}s will be called
*/
protected Handler getJobHandler() {
- return Handler.getMain();
+ return mMainHandler;
}
/**
@@ -391,8 +387,7 @@
private boolean enqueue(@NonNull Job<I, ?> job) {
cancelTimeout();
- return getJobHandler().sendMessage(PooledLambda.obtainMessage(
- ServiceConnector.Impl::enqueueJobThread, this, job));
+ return getJobHandler().post(() -> enqueueJobThread(job));
}
void enqueueJobThread(@NonNull Job<I, ?> job) {
@@ -422,7 +417,7 @@
if (DEBUG) {
logTrace();
}
- Handler.getMain().removeCallbacks(mTimeoutDisconnect);
+ mMainHandler.removeCallbacks(mTimeoutDisconnect);
}
void completeExceptionally(@NonNull Job<?, ?> job, @NonNull Throwable ex) {
@@ -486,7 +481,7 @@
}
long timeout = getAutoDisconnectTimeoutMs();
if (timeout > 0) {
- Handler.getMain().postDelayed(mTimeoutDisconnect, timeout);
+ mMainHandler.postDelayed(mTimeoutDisconnect, timeout);
} else if (DEBUG) {
Log.i(LOG_TAG, "Not scheduling unbind for permanently bound " + this);
}
@@ -502,7 +497,7 @@
logTrace();
}
mUnbinding = true;
- getJobHandler().sendMessage(PooledLambda.obtainMessage(Impl::unbindJobThread, this));
+ getJobHandler().post(this::unbindJobThread);
}
void unbindJobThread() {
@@ -659,10 +654,7 @@
}
private void logTrace() {
- Log.i(LOG_TAG,
- TextUtils.join(" -> ",
- DebugUtils.callersWithin(ServiceConnector.class, /* offset= */ 1))
- + "(" + this + ")");
+ Log.i(LOG_TAG, "See stacktrace", new Throwable());
}
/**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f1874ea..63ac1c7 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5055,6 +5055,10 @@
<!-- Allows input events to be monitored. Very dangerous! @hide -->
<permission android:name="android.permission.MONITOR_INPUT"
android:protectionLevel="signature" />
+ <!-- Allows the use of FLAG_SLIPPERY, which permits touch events to slip from the current
+ window to the window where the touch currently is on top of. @hide -->
+ <permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES"
+ android:protectionLevel="signature" />
<!-- Allows the caller to change the associations between input devices and displays.
Very dangerous! @hide -->
<permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT"
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index a16d07b..6ef7b0f 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -115,6 +115,7 @@
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.MONITOR_INPUT" />
<uses-permission android:name="android.permission.INPUT_CONSUMER" />
+ <uses-permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES" />
<!-- DreamManager -->
<uses-permission android:name="android.permission.READ_DREAM_STATE" />
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
index a687bb89f..5c742f2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
@@ -201,7 +201,6 @@
filter.addAction(DISABLE_PLUGIN);
filter.addDataScheme("package");
mContext.registerReceiver(this, filter, PluginInstanceManager.PLUGIN_PERMISSION, null);
- mContext.registerReceiver(this, filter);
filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
mContext.registerReceiver(this, filter);
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
index 977e46a..d2ded71 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
@@ -131,6 +131,12 @@
wrapper = null
bindService(false)
}
+
+ override fun onNullBinding(name: ComponentName?) {
+ if (DEBUG) Log.d(TAG, "onNullBinding $name")
+ wrapper = null
+ context.unbindService(this)
+ }
}
private fun handlePendingServiceMethods() {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index e15380b..f6b420b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -17,6 +17,7 @@
package com.android.systemui.controls.ui
import android.app.Dialog
+import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
@@ -74,7 +75,7 @@
bouncerOrRun(Action(cvh.cws.ci.controlId, {
cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
if (cvh.usePanel()) {
- showDialog(cvh, control.getAppIntent().getIntent())
+ showDetail(cvh, control.getAppIntent())
} else {
cvh.action(CommandAction(templateId))
}
@@ -100,7 +101,7 @@
// Long press snould only be called when there is valid control state, otherwise ignore
cvh.cws.control?.let {
cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
- showDialog(cvh, it.getAppIntent().getIntent())
+ showDetail(cvh, it.getAppIntent())
}
}, false /* blockable */))
}
@@ -155,17 +156,17 @@
bgExecutor.execute { vibrator.vibrate(effect) }
}
- private fun showDialog(cvh: ControlViewHolder, intent: Intent) {
+ private fun showDetail(cvh: ControlViewHolder, pendingIntent: PendingIntent) {
bgExecutor.execute {
- val activities: List<ResolveInfo> = cvh.context.packageManager.queryIntentActivities(
- intent,
+ val activities: List<ResolveInfo> = context.packageManager.queryIntentActivities(
+ pendingIntent.getIntent(),
PackageManager.MATCH_DEFAULT_ONLY
)
uiExecutor.execute {
// make sure the intent is valid before attempting to open the dialog
if (activities.isNotEmpty()) {
- dialog = DetailDialog(cvh, intent).also {
+ dialog = DetailDialog(cvh, pendingIntent).also {
it.setOnDismissListener { _ -> dialog = null }
it.show()
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index 9ec1452..a7f7eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -16,8 +16,11 @@
package com.android.systemui.controls.ui
+import android.app.ActivityOptions
import android.app.ActivityView
+import android.app.PendingIntent
import android.app.Dialog
+import android.content.ComponentName
import android.content.Intent
import android.provider.Settings
import android.view.View
@@ -37,9 +40,8 @@
*/
class DetailDialog(
val cvh: ControlViewHolder,
- val intent: Intent
+ val pendingIntent: PendingIntent
) : Dialog(cvh.context, R.style.Theme_SystemUI_Dialog_Control_DetailPanel) {
-
companion object {
private const val PANEL_TOP_OFFSET = "systemui.controls_panel_top_offset"
/*
@@ -49,18 +51,19 @@
private const val EXTRA_USE_PANEL = "controls.DISPLAY_IN_PANEL"
}
+ private val fillInIntent = Intent().apply {
+ putExtra(EXTRA_USE_PANEL, true)
+
+ // Apply flags to make behaviour match documentLaunchMode=always.
+ addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
+ addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
+ }
+
var activityView = ActivityView(context, null, 0, false)
val stateCallback: ActivityView.StateCallback = object : ActivityView.StateCallback() {
override fun onActivityViewReady(view: ActivityView) {
- val launchIntent = Intent(intent)
- launchIntent.putExtra(EXTRA_USE_PANEL, true)
-
- // Apply flags to make behaviour match documentLaunchMode=always.
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
-
- view.startActivity(launchIntent)
+ view.startActivity(pendingIntent, fillInIntent, ActivityOptions.makeBasic())
}
override fun onActivityViewDestroyed(view: ActivityView) {}
@@ -68,6 +71,12 @@
override fun onTaskRemovalStarted(taskId: Int) {
dismiss()
}
+
+ override fun onTaskCreated(taskId: Int, name: ComponentName?) {
+ requireViewById<ViewGroup>(R.id.controls_activity_view).apply {
+ setAlpha(1f)
+ }
+ }
}
init {
@@ -76,6 +85,7 @@
requireViewById<ViewGroup>(R.id.controls_activity_view).apply {
addView(activityView)
+ setAlpha(0f)
}
requireViewById<ImageView>(R.id.control_detail_close).apply {
@@ -86,7 +96,7 @@
setOnClickListener { v: View ->
dismiss()
context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
- v.context.startActivity(intent)
+ pendingIntent.send()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 2bef355..e0e603f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -134,11 +134,11 @@
}
@Override
- public boolean setState(int state) {
+ public boolean setState(int state, boolean force) {
if (state > MAX_STATE || state < MIN_STATE) {
throw new IllegalArgumentException("Invalid state " + state);
}
- if (state == mState) {
+ if (!force && state == mState) {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index 07b3550..fc40077 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -58,7 +58,19 @@
* @param state see {@link StatusBarState} for valid options
* @return {@code true} if the state changed, else {@code false}
*/
- boolean setState(int state);
+ default boolean setState(int state) {
+ return setState(state, false /* force */);
+ }
+
+ /**
+ * Update the status bar state
+ * @param state see {@link StatusBarState} for valid options
+ * @param force whether to set the state even if it's the same as the current state. This will
+ * dispatch the state to all StatusBarStateListeners, ensuring that all listening
+ * components are reset to this state.
+ * @return {@code true} if the state was changed or set forcefully
+ */
+ boolean setState(int state, boolean force);
/**
* Update the dozing state from {@link StatusBar}'s perspective
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 18ed5de..057b68c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -3186,6 +3186,10 @@
}
boolean updateIsKeyguard() {
+ return updateIsKeyguard(false /* force */);
+ }
+
+ boolean updateIsKeyguard(boolean force) {
boolean wakeAndUnlocking = mBiometricUnlockController.getMode()
== BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -3208,7 +3212,7 @@
showKeyguardImpl();
}
} else {
- return hideKeyguardImpl();
+ return hideKeyguardImpl(force);
}
return false;
}
@@ -3339,11 +3343,11 @@
/**
* @return true if we would like to stay in the shade, false if it should go away entirely
*/
- public boolean hideKeyguardImpl() {
+ public boolean hideKeyguardImpl(boolean force) {
mIsKeyguard = false;
Trace.beginSection("StatusBar#hideKeyguard");
boolean staying = mStatusBarStateController.leaveOpenOnKeyguardHide();
- if (!(mStatusBarStateController.setState(StatusBarState.SHADE))) {
+ if (!(mStatusBarStateController.setState(StatusBarState.SHADE, force))) {
//TODO: StatusBarStateController should probably know about hiding the keyguard and
// notify listeners.
@@ -3770,7 +3774,8 @@
// is correct.
mHandler.post(() -> onCameraLaunchGestureDetected(mLastCameraLaunchSource));
}
- updateIsKeyguard();
+ // When finished going to sleep, force the status bar state to avoid stale state.
+ updateIsKeyguard(true /* force */);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
index 2d3757c..12096bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
@@ -17,6 +17,9 @@
package com.android.systemui.controls.controller
import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
import android.os.UserHandle
import android.service.controls.IControlsActionCallback
import android.service.controls.IControlsProvider
@@ -43,6 +46,8 @@
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -57,8 +62,6 @@
private lateinit var subscriberService: IControlsSubscriber.Stub
@Mock
private lateinit var service: IControlsProvider.Stub
- @Mock
- private lateinit var loadCallback: ControlsBindingController.LoadCallback
@Captor
private lateinit var wrapperCaptor: ArgumentCaptor<ControlActionWrapper>
@@ -75,7 +78,7 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- mContext.addMockService(componentName, service)
+ context.addMockService(componentName, service)
executor = FakeExecutor(FakeSystemClock())
`when`(service.asBinder()).thenCallRealMethod()
`when`(service.queryLocalInterface(ArgumentMatchers.anyString())).thenReturn(service)
@@ -98,7 +101,36 @@
fun testBindService() {
manager.bindService()
executor.runAllReady()
- assertTrue(mContext.isBound(componentName))
+ assertTrue(context.isBound(componentName))
+ }
+
+ @Test
+ fun testNullBinding() {
+ val mockContext = mock(Context::class.java)
+ lateinit var serviceConnection: ServiceConnection
+ `when`(mockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer {
+ val component = (it.arguments[0] as Intent).component
+ if (component == componentName) {
+ serviceConnection = it.arguments[1] as ServiceConnection
+ serviceConnection.onNullBinding(component)
+ true
+ } else {
+ false
+ }
+ }
+
+ val nullManager = ControlsProviderLifecycleManager(
+ mockContext,
+ executor,
+ actionCallbackService,
+ UserHandle.of(0),
+ componentName
+ )
+
+ nullManager.bindService()
+ executor.runAllReady()
+
+ verify(mockContext).unbindService(serviceConnection)
}
@Test
@@ -109,7 +141,7 @@
manager.unbindService()
executor.runAllReady()
- assertFalse(mContext.isBound(componentName))
+ assertFalse(context.isBound(componentName))
}
@Test
@@ -119,7 +151,7 @@
verify(service).load(subscriberService)
- assertTrue(mContext.isBound(componentName))
+ assertTrue(context.isBound(componentName))
}
@Test
@@ -129,7 +161,7 @@
manager.unbindService()
executor.runAllReady()
- assertFalse(mContext.isBound(componentName))
+ assertFalse(context.isBound(componentName))
}
@Test
@@ -162,7 +194,7 @@
manager.maybeBindAndSubscribe(list, subscriberService)
executor.runAllReady()
- assertTrue(mContext.isBound(componentName))
+ assertTrue(context.isBound(componentName))
verify(service).subscribe(list, subscriberService)
}
@@ -173,7 +205,7 @@
manager.maybeBindAndSendAction(controlId, action)
executor.runAllReady()
- assertTrue(mContext.isBound(componentName))
+ assertTrue(context.isBound(componentName))
verify(service).action(eq(controlId), capture(wrapperCaptor),
eq(actionCallbackService))
assertEquals(action, wrapperCaptor.getValue().getWrappedAction())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
new file mode 100644
index 0000000..0ad03d2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.ui
+
+import android.app.ActivityView
+import android.app.PendingIntent
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.capture
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class DetailDialogTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var activityView: ActivityView
+ @Mock
+ private lateinit var controlViewHolder: ControlViewHolder
+ @Mock
+ private lateinit var pendingIntent: PendingIntent
+ @Captor
+ private lateinit var viewCaptor: ArgumentCaptor<ActivityView>
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testPendingIntentIsUnModified() {
+ // GIVEN the dialog is created with a PendingIntent
+ val dialog = createDialog(pendingIntent)
+
+ // WHEN the ActivityView is initialized
+ dialog.stateCallback.onActivityViewReady(capture(viewCaptor))
+
+ // THEN the PendingIntent used to call startActivity is unmodified by systemui
+ verify(viewCaptor.value).startActivity(eq(pendingIntent), any(), any())
+ }
+
+ private fun createDialog(pendingIntent: PendingIntent): DetailDialog {
+ return DetailDialog(
+ controlViewHolder,
+ pendingIntent
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 5a08c9c..d528f20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -809,12 +809,14 @@
// By default, showKeyguardImpl sets state to KEYGUARD.
mStatusBar.showKeyguardImpl();
- verify(mStatusBarStateController).setState(eq(StatusBarState.KEYGUARD));
+ verify(mStatusBarStateController).setState(
+ eq(StatusBarState.KEYGUARD), eq(false) /* force */);
// If useFullscreenUserSwitcher is true, state is set to FULLSCREEN_USER_SWITCHER.
when(mUserSwitcherController.useFullscreenUserSwitcher()).thenReturn(true);
mStatusBar.showKeyguardImpl();
- verify(mStatusBarStateController).setState(eq(StatusBarState.FULLSCREEN_USER_SWITCHER));
+ verify(mStatusBarStateController).setState(
+ eq(StatusBarState.FULLSCREEN_USER_SWITCHER), eq(false) /* force */);
}
@Test
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 633bdcc..bd205c5 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1822,6 +1822,11 @@
+ ", skipping since the account already exists");
return false;
}
+ if (accounts.accountsDb.findAllDeAccounts().size() > 100) {
+ Log.w(TAG, "insertAccountIntoDatabase: " + account.toSafeString()
+ + ", skipping since more than 50 accounts on device exist");
+ return false;
+ }
long accountId = accounts.accountsDb.insertCeAccount(account, password);
if (accountId < 0) {
Log.w(TAG, "insertAccountIntoDatabase: " + account.toSafeString()
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index ed83a64..95da946 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -18,6 +18,7 @@
import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull;
+import android.annotation.NonNull;
import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.Notification;
@@ -171,6 +172,12 @@
mAdbConnectionInfo = new AdbConnectionInfo();
}
+ static void sendBroadcastWithDebugPermission(@NonNull Context context, @NonNull Intent intent,
+ @NonNull UserHandle userHandle) {
+ context.sendBroadcastAsUser(intent, userHandle,
+ android.Manifest.permission.MANAGE_DEBUGGING);
+ }
+
class PairingThread extends Thread implements NsdManager.RegistrationListener {
private NsdManager mNsdManager;
private String mPublicKey;
@@ -1279,7 +1286,7 @@
? AdbManager.WIRELESS_STATUS_CONNECTED
: AdbManager.WIRELESS_STATUS_DISCONNECTED);
intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL);
}
private void onAdbdWifiServerConnected(int port) {
@@ -1351,7 +1358,8 @@
if (publicKey == null) {
Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA, AdbManager.WIRELESS_STATUS_FAIL);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent,
+ UserHandle.ALL);
} else {
Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA,
@@ -1364,7 +1372,8 @@
}
PairDevice device = new PairDevice(fingerprints, hostname, false);
intent.putExtra(AdbManager.WIRELESS_PAIR_DEVICE_EXTRA, device);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent,
+ UserHandle.ALL);
// Add the key into the keystore
mAdbKeyStore.setLastConnectionTime(publicKey,
System.currentTimeMillis());
@@ -1378,14 +1387,14 @@
intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA,
AdbManager.WIRELESS_STATUS_CONNECTED);
intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL);
}
private void sendPairedDevicesToUI(Map<String, PairDevice> devices) {
Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION);
// Map is not serializable, so need to downcast
intent.putExtra(AdbManager.WIRELESS_DEVICES_EXTRA, (HashMap) devices);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL);
}
private void updateUIPairCode(String code) {
@@ -1395,7 +1404,7 @@
intent.putExtra(AdbManager.WIRELESS_PAIRING_CODE_EXTRA, code);
intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA,
AdbManager.WIRELESS_STATUS_PAIRING_CODE);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL);
}
}
diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java
index 29bb542..5b16daa 100644
--- a/services/core/java/com/android/server/adb/AdbService.java
+++ b/services/core/java/com/android/server/adb/AdbService.java
@@ -431,7 +431,7 @@
? AdbManager.WIRELESS_STATUS_CONNECTED
: AdbManager.WIRELESS_STATUS_DISCONNECTED);
intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL);
Slog.i(TAG, "sent port broadcast port=" + port);
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index dafd8d8..6f183f5 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -770,11 +770,8 @@
}
ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
- if (!r.mAllowWhileInUsePermissionInFgs) {
- r.mAllowWhileInUsePermissionInFgs =
- shouldAllowWhileInUsePermissionInFgsLocked(callingPackage, callingPid,
- callingUid, service, r, allowBackgroundActivityStarts);
- }
+ setFgsRestrictionLocked(callingPackage, callingPid, callingUid, r,
+ allowBackgroundActivityStarts);
return cmp;
}
@@ -1447,14 +1444,6 @@
+ String.format("0x%08X", manifestType)
+ " in service element of manifest file");
}
- // If the foreground service is not started from TOP process, do not allow it to
- // have while-in-use location/camera/microphone access.
- if (!r.mAllowWhileInUsePermissionInFgs) {
- Slog.w(TAG,
- "Foreground service started from background can not have "
- + "location/camera/microphone access: service "
- + r.shortInstanceName);
- }
}
boolean alreadyStartedOp = false;
boolean stopProcStatsOp = false;
@@ -1502,6 +1491,56 @@
ignoreForeground = true;
}
+ if (!ignoreForeground) {
+ if (r.mStartForegroundCount == 0) {
+ /*
+ If the service was started with startService(), not
+ startForegroundService(), and if startForeground() isn't called within
+ mFgsStartForegroundTimeoutMs, then we check the state of the app
+ (who owns the service, which is the app that called startForeground())
+ again. If the app is in the foreground, or in any other cases where
+ FGS-starts are allowed, then we still allow the FGS to be started.
+ Otherwise, startForeground() would fail.
+
+ If the service was started with startForegroundService(), then the service
+ must call startForeground() within a timeout anyway, so we don't need this
+ check.
+ */
+ if (!r.fgRequired) {
+ final long delayMs = SystemClock.elapsedRealtime() - r.createRealTime;
+ if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) {
+ resetFgsRestrictionLocked(r);
+ setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.pid,
+ r.appInfo.uid, r, false);
+ EventLog.writeEvent(0x534e4554, "183147114",
+ r.appInfo.uid,
+ "call setFgsRestrictionLocked again due to "
+ + "startForegroundTimeout");
+ }
+ }
+ } else if (r.mStartForegroundCount >= 1) {
+ // The second or later time startForeground() is called after service is
+ // started. Check for app state again.
+ final long delayMs = SystemClock.elapsedRealtime() -
+ r.mLastSetFgsRestrictionTime;
+ if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) {
+ setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.pid,
+ r.appInfo.uid, r, false);
+ EventLog.writeEvent(0x534e4554, "183147114", r.appInfo.uid,
+ "call setFgsRestrictionLocked for "
+ + (r.mStartForegroundCount + 1) + "th startForeground");
+ }
+ }
+ // If the foreground service is not started from TOP process, do not allow it to
+ // have while-in-use location/camera/microphone access.
+ if (!r.mAllowWhileInUsePermissionInFgs) {
+ Slog.w(TAG,
+ "Foreground service started from background can not have "
+ + "location/camera/microphone access: service "
+ + r.shortInstanceName);
+ }
+ }
+
// Apps under strict background restrictions simply don't get to have foreground
// services, so now that we've enforced the startForegroundService() contract
// we only do the machinery of making the service foreground when the app
@@ -1537,6 +1576,7 @@
active.mNumActive++;
}
r.isForeground = true;
+ r.mStartForegroundCount++;
if (!stopProcStatsOp) {
ServiceState stracker = r.getTracker();
if (stracker != null) {
@@ -1595,6 +1635,7 @@
decActiveForegroundAppLocked(smap, r);
}
r.isForeground = false;
+ resetFgsRestrictionLocked(r);
ServiceState stracker = r.getTracker();
if (stracker != null) {
stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(),
@@ -2178,12 +2219,7 @@
}
}
- if (!s.mAllowWhileInUsePermissionInFgs) {
- s.mAllowWhileInUsePermissionInFgs =
- shouldAllowWhileInUsePermissionInFgsLocked(callingPackage,
- callingPid, callingUid,
- service, s, false);
- }
+ setFgsRestrictionLocked(callingPackage, callingPid, callingUid, s, false);
if (s.app != null) {
if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
@@ -3597,7 +3633,7 @@
r.isForeground = false;
r.foregroundId = 0;
r.foregroundNoti = null;
- r.mAllowWhileInUsePermissionInFgs = false;
+ resetFgsRestrictionLocked(r);
// Clear start entries.
r.clearDeliveredStartsLocked();
@@ -5087,7 +5123,7 @@
* @return true if allow, false otherwise.
*/
private boolean shouldAllowWhileInUsePermissionInFgsLocked(String callingPackage,
- int callingPid, int callingUid, Intent intent, ServiceRecord r,
+ int callingPid, int callingUid, ServiceRecord r,
boolean allowBackgroundActivityStarts) {
// Is the background FGS start restriction turned on?
if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
@@ -5173,6 +5209,29 @@
boolean canAllowWhileInUsePermissionInFgsLocked(int callingPid, int callingUid,
String callingPackage) {
return shouldAllowWhileInUsePermissionInFgsLocked(
- callingPackage, callingPid, callingUid, null, null, false);
+ callingPackage, callingPid, callingUid, null, false);
+ }
+
+ /**
+ * In R, mAllowWhileInUsePermissionInFgs is to allow while-in-use permissions in foreground
+ * service or not. while-in-use permissions in FGS started from background might be restricted.
+ * @param callingPackage caller app's package name.
+ * @param callingUid caller app's uid.
+ * @param r the service to start.
+ * @return true if allow, false otherwise.
+ */
+ private void setFgsRestrictionLocked(String callingPackage,
+ int callingPid, int callingUid, ServiceRecord r,
+ boolean allowBackgroundActivityStarts) {
+ r.mLastSetFgsRestrictionTime = SystemClock.elapsedRealtime();
+ if (!r.mAllowWhileInUsePermissionInFgs) {
+ r.mAllowWhileInUsePermissionInFgs = shouldAllowWhileInUsePermissionInFgsLocked(
+ callingPackage, callingPid, callingUid, r, allowBackgroundActivityStarts);
+ }
+ }
+
+ private void resetFgsRestrictionLocked(ServiceRecord r) {
+ r.mAllowWhileInUsePermissionInFgs = false;
+ r.mLastSetFgsRestrictionTime = 0;
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index a9b2d89..115eba1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -91,6 +91,7 @@
static final String KEY_PROCESS_START_ASYNC = "process_start_async";
static final String KEY_MEMORY_INFO_THROTTLE_TIME = "memory_info_throttle_time";
static final String KEY_TOP_TO_FGS_GRACE_DURATION = "top_to_fgs_grace_duration";
+ static final String KEY_FGS_START_FOREGROUND_TIMEOUT = "fgs_start_foreground_timeout";
static final String KEY_PENDINGINTENT_WARNING_THRESHOLD = "pendingintent_warning_threshold";
private static int DEFAULT_MAX_CACHED_PROCESSES = 32;
@@ -124,6 +125,7 @@
private static final boolean DEFAULT_PROCESS_START_ASYNC = true;
private static final long DEFAULT_MEMORY_INFO_THROTTLE_TIME = 5*60*1000;
private static final long DEFAULT_TOP_TO_FGS_GRACE_DURATION = 15 * 1000;
+ private static final int DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS = 10 * 1000;
private static final int DEFAULT_PENDINGINTENT_WARNING_THRESHOLD = 2000;
// Flag stored in the DeviceConfig API.
@@ -276,6 +278,12 @@
// this long.
public long TOP_TO_FGS_GRACE_DURATION = DEFAULT_TOP_TO_FGS_GRACE_DURATION;
+ /**
+ * When service started from background, before the timeout it can be promoted to FGS by calling
+ * Service.startForeground().
+ */
+ volatile long mFgsStartForegroundTimeoutMs = DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS;
+
// Indicates whether the activity starts logging is enabled.
// Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED
volatile boolean mFlagActivityStartsLoggingEnabled;
@@ -433,6 +441,9 @@
case KEY_MIN_ASSOC_LOG_DURATION:
updateMinAssocLogDuration();
break;
+ case KEY_FGS_START_FOREGROUND_TIMEOUT:
+ updateFgsStartForegroundTimeout();
+ break;
default:
break;
}
@@ -754,6 +765,13 @@
/* defaultValue */ DEFAULT_MIN_ASSOC_LOG_DURATION);
}
+ private void updateFgsStartForegroundTimeout() {
+ mFgsStartForegroundTimeoutMs = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_FGS_START_FOREGROUND_TIMEOUT,
+ DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS);
+ }
+
void dump(PrintWriter pw) {
pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) "
+ Settings.Global.ACTIVITY_MANAGER_CONSTANTS + ":");
@@ -826,6 +844,8 @@
pw.println(Arrays.toString(IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.toArray()));
pw.print(" "); pw.print(KEY_MIN_ASSOC_LOG_DURATION); pw.print("=");
pw.println(MIN_ASSOC_LOG_DURATION);
+ pw.print(" "); pw.print(KEY_FGS_START_FOREGROUND_TIMEOUT); pw.print("=");
+ pw.println(mFgsStartForegroundTimeoutMs);
pw.println();
if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 1b65dba..0e62828 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -142,6 +142,10 @@
// allow while-in-use permissions in foreground service or not.
// while-in-use permissions in FGS started from background might be restricted.
boolean mAllowWhileInUsePermissionInFgs;
+ // The number of times Service.startForeground() is called;
+ int mStartForegroundCount;
+ // Last time mAllowWhileInUsePermissionInFgs is set.
+ long mLastSetFgsRestrictionTime;
// the most recent package that start/bind this service.
String mRecentCallingPackage;
@@ -406,6 +410,8 @@
}
pw.print(prefix); pw.print("allowWhileInUsePermissionInFgs=");
pw.println(mAllowWhileInUsePermissionInFgs);
+ pw.print(prefix); pw.print("startForegroundCount=");
+ pw.println(mStartForegroundCount);
pw.print(prefix); pw.print("recentCallingPackage=");
pw.println(mRecentCallingPackage);
if (delayed) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 629a918..c3e3988 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2478,7 +2478,7 @@
}
}
- private void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
+ void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
boolean fromApp, boolean fromListener) {
Objects.requireNonNull(group);
Objects.requireNonNull(pkg);
@@ -3498,7 +3498,8 @@
final int callingUid = Binder.getCallingUid();
NotificationChannelGroup groupToDelete =
- mPreferencesHelper.getNotificationChannelGroup(groupId, pkg, callingUid);
+ mPreferencesHelper.getNotificationChannelGroupWithChannels(
+ pkg, callingUid, groupId, false);
if (groupToDelete != null) {
// Preflight for allowability
final int userId = UserHandle.getUserId(callingUid);
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index fd4a302..98d9e9a 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -777,6 +777,9 @@
if (r == null) {
throw new IllegalArgumentException("Invalid package");
}
+ if (fromTargetApp) {
+ group.setBlocked(false);
+ }
final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
if (oldGroup != null) {
group.setChannels(oldGroup.getChannels());
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 8516033..9569818 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -134,6 +134,9 @@
/** Upper bound on number of historical sessions for a UID */
private static final long MAX_HISTORICAL_SESSIONS = 1048576;
+ /** Destroy sessions older than this on storage free request */
+ private static final long MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS = 8 * DateUtils.HOUR_IN_MILLIS;
+
/**
* Allow verification-skipping if it's a development app installed through ADB with
* disable verification flag specified.
@@ -295,22 +298,28 @@
@GuardedBy("mSessions")
private void reconcileStagesLocked(String volumeUuid) {
- final File stagingDir = getTmpSessionDir(volumeUuid);
- final ArraySet<File> unclaimedStages = newArraySet(
- stagingDir.listFiles(sStageFilter));
-
- // We also need to clean up orphaned staging directory for staged sessions
- final File stagedSessionStagingDir = Environment.getDataStagingDirectory(volumeUuid);
- unclaimedStages.addAll(newArraySet(stagedSessionStagingDir.listFiles()));
-
+ final ArraySet<File> unclaimedStages = getStagingDirsOnVolume(volumeUuid);
// Ignore stages claimed by active sessions
for (int i = 0; i < mSessions.size(); i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
unclaimedStages.remove(session.stageDir);
}
+ removeStagingDirs(unclaimedStages);
+ }
+ private ArraySet<File> getStagingDirsOnVolume(String volumeUuid) {
+ final File stagingDir = getTmpSessionDir(volumeUuid);
+ final ArraySet<File> stagingDirs = newArraySet(stagingDir.listFiles(sStageFilter));
+
+ // We also need to clean up orphaned staging directory for staged sessions
+ final File stagedSessionStagingDir = Environment.getDataStagingDirectory(volumeUuid);
+ stagingDirs.addAll(newArraySet(stagedSessionStagingDir.listFiles()));
+ return stagingDirs;
+ }
+
+ private void removeStagingDirs(ArraySet<File> stagingDirsToRemove) {
// Clean up orphaned staging directories
- for (File stage : unclaimedStages) {
+ for (File stage : stagingDirsToRemove) {
Slog.w(TAG, "Deleting orphan stage " + stage);
synchronized (mPm.mInstallLock) {
mPm.removeCodePathLI(stage);
@@ -318,6 +327,38 @@
}
}
+ /**
+ * Called to free up some storage space from obsolete installation files
+ */
+ public void freeStageDirs(String volumeUuid) {
+ final ArraySet<File> unclaimedStagingDirsOnVolume = getStagingDirsOnVolume(volumeUuid);
+ final long currentTimeMillis = System.currentTimeMillis();
+ synchronized (mSessions) {
+ for (int i = 0; i < mSessions.size(); i++) {
+ final PackageInstallerSession session = mSessions.valueAt(i);
+ if (!unclaimedStagingDirsOnVolume.contains(session.stageDir)) {
+ // Only handles sessions stored on the target volume
+ continue;
+ }
+ final long age = currentTimeMillis - session.createdMillis;
+ if (age >= MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS) {
+ // Aggressively close old sessions because we are running low on storage
+ // Their staging dirs will be removed too
+ PackageInstallerSession root = !session.hasParentSessionId()
+ ? session : mSessions.get(session.getParentSessionId());
+ if (!root.isDestroyed()) {
+ root.abandon();
+ }
+ } else {
+ // Session is new enough, so it deserves to be kept even on low storage
+ unclaimedStagingDirsOnVolume.remove(session.stageDir);
+ }
+ }
+ }
+ removeStagingDirs(unclaimedStagingDirsOnVolume);
+ }
+
+
public void onPrivateVolumeMounted(String volumeUuid) {
synchronized (mSessions) {
reconcileStagesLocked(volumeUuid);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 8020e33..69843bb 100755
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2662,14 +2662,8 @@
List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
synchronized (mLock) {
- if (params.isStaged && mDestroyed) {
- // If a user abandons staged session in an unsafe state, then system will try to
- // abandon the destroyed staged session when it is safe on behalf of the user.
- assertCallerIsOwnerOrRootOrSystemLocked();
- } else {
- assertCallerIsOwnerOrRootLocked();
- }
+ assertCallerIsOwnerOrRootOrSystemLocked();
if (isStagedAndInTerminalState()) {
// We keep the session in the database if it's in a finalized state. It will be
// removed by PackageInstallerService when the last update time is old enough.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5d98de1..323ee21c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5175,6 +5175,10 @@
InstantAppRegistry.DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) {
return;
}
+
+ // 12. Clear temp install session files
+ mInstallerService.freeStageDirs(volumeUuid);
+
} else {
try {
mInstaller.freeCache(volumeUuid, bytes, 0, 0);
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index 5e04171..354f5b9 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -409,6 +409,8 @@
}
if (bp.perm != null && Objects.equals(bp.perm.getPackageName(), p.getPackageName())
&& Objects.equals(bp.perm.getName(), p.getName())) {
+ bp.perm.setFlags(bp.perm.getFlags() & ~PermissionInfo.FLAG_INSTALLED);
+ bp.perm = p.setFlags(p.getFlags() | PermissionInfo.FLAG_INSTALLED);
bp.protectionLevel = p.getProtectionLevel();
}
if (bp.isRuntime() && (ownerChanged || wasNonRuntime)) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 2945ca0..fd14f9d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -56,6 +56,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
@@ -1575,6 +1576,7 @@
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
displayPolicy.adjustWindowParamsLw(win, win.mAttrs, callingPid, callingUid);
+ attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
if (res != WindowManagerGlobal.ADD_OKAY) {
@@ -2159,6 +2161,7 @@
if (attrs != null) {
displayPolicy.adjustWindowParamsLw(win, attrs, pid, uid);
win.mToken.adjustWindowParams(win, attrs);
+ attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), uid, pid);
// if they don't have the permission, mask out the status bar bits
if (seq == win.mSeq) {
int systemUiVisibility = attrs.systemUiVisibility
@@ -8080,6 +8083,23 @@
}
/**
+ * You need ALLOW_SLIPPERY_TOUCHES permission to be able to set FLAG_SLIPPERY.
+ */
+ private int sanitizeFlagSlippery(int flags, String windowName, int callingUid, int callingPid) {
+ if ((flags & FLAG_SLIPPERY) == 0) {
+ return flags;
+ }
+ final int permissionResult = mContext.checkPermission(
+ android.Manifest.permission.ALLOW_SLIPPERY_TOUCHES, callingPid, callingUid);
+ if (permissionResult != PackageManager.PERMISSION_GRANTED) {
+ Slog.w(TAG, "Removing FLAG_SLIPPERY from '" + windowName
+ + "' because it doesn't have ALLOW_SLIPPERY_TOUCHES permission");
+ return flags & ~FLAG_SLIPPERY;
+ }
+ return flags;
+ }
+
+ /**
* Assigns an InputChannel to a SurfaceControl and configures it to receive
* touch input according to it's on-screen geometry.
*
@@ -8116,8 +8136,9 @@
h.token = channelToken;
h.name = name;
- final int sanitizedFlags = flags & (LayoutParams.FLAG_NOT_TOUCHABLE
- | LayoutParams.FLAG_SLIPPERY);
+ flags = sanitizeFlagSlippery(flags, name, callingUid, callingPid);
+
+ final int sanitizedFlags = flags & (LayoutParams.FLAG_NOT_TOUCHABLE | FLAG_SLIPPERY);
h.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | sanitizedFlags;
h.layoutParamsType = type;
h.dispatchingTimeoutNanos = DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ae354a9..27d07ab 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9396,6 +9396,9 @@
final ComponentName doComponent = mOwners.getDeviceOwnerComponent();
final ComponentName poComponent =
mOwners.getProfileOwnerComponent(userHandle.getIdentifier());
+ if (supervisorComponent == null) {
+ return null;
+ }
if (supervisorComponent.equals(doComponent) || supervisorComponent.equals(
poComponent)) {
return supervisorComponent;
@@ -12482,6 +12485,13 @@
}
@Override
+ public @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent(
+ @NonNull UserHandle userHandle) {
+ return DevicePolicyManagerService.this.getProfileOwnerOrDeviceOwnerSupervisionComponent(
+ userHandle);
+ }
+
+ @Override
public boolean isActiveAdminWithPolicy(int uid, int reqPolicy) {
synchronized (getLockObject()) {
return getActiveAdminWithPolicyForUidLocked(null, reqPolicy, uid) != null;
diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
index cffff66..02cf971 100644
--- a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
@@ -23,7 +23,14 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.debug.AdbManager;
+import android.debug.IAdbManager;
+import android.os.ServiceManager;
import android.provider.Settings;
import android.util.Log;
@@ -105,6 +112,7 @@
public void tearDown() throws Exception {
mKeyStore.deleteKeyStore();
setAllowedConnectionTime(mOriginalAllowedConnectionTime);
+ dropShellPermissionIdentity();
}
/**
@@ -813,6 +821,108 @@
return hasAtLeastOneLetter;
}
+ CountDownLatch mAdbActionLatch = new CountDownLatch(1);
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ Log.i(TAG, "Received intent action=" + action);
+ if (AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION.equals(action)) {
+ assertEquals("Received broadcast without MANAGE_DEBUGGING permission.",
+ context.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING),
+ PackageManager.PERMISSION_GRANTED);
+ Log.i(TAG, "action=" + action + " paired_device=" + intent.getSerializableExtra(
+ AdbManager.WIRELESS_DEVICES_EXTRA).toString());
+ mAdbActionLatch.countDown();
+ } else if (AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION.equals(action)) {
+ assertEquals("Received broadcast without MANAGE_DEBUGGING permission.",
+ context.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING),
+ PackageManager.PERMISSION_GRANTED);
+ int status = intent.getIntExtra(AdbManager.WIRELESS_STATUS_EXTRA,
+ AdbManager.WIRELESS_STATUS_DISCONNECTED);
+ Log.i(TAG, "action=" + action + " status=" + status);
+ mAdbActionLatch.countDown();
+ } else if (AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION.equals(action)) {
+ assertEquals("Received broadcast without MANAGE_DEBUGGING permission.",
+ context.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING),
+ PackageManager.PERMISSION_GRANTED);
+ Integer res = intent.getIntExtra(
+ AdbManager.WIRELESS_STATUS_EXTRA,
+ AdbManager.WIRELESS_STATUS_FAIL);
+ Log.i(TAG, "action=" + action + " result=" + res);
+
+ if (res.equals(AdbManager.WIRELESS_STATUS_PAIRING_CODE)) {
+ String pairingCode = intent.getStringExtra(
+ AdbManager.WIRELESS_PAIRING_CODE_EXTRA);
+ Log.i(TAG, "pairingCode=" + pairingCode);
+ } else if (res.equals(AdbManager.WIRELESS_STATUS_CONNECTED)) {
+ int port = intent.getIntExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, 0);
+ Log.i(TAG, "port=" + port);
+ }
+ mAdbActionLatch.countDown();
+ }
+ }
+ };
+
+ private void adoptShellPermissionIdentity() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(android.Manifest.permission.MANAGE_DEBUGGING);
+ }
+
+ private void dropShellPermissionIdentity() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ @Test
+ public void testBroadcastReceiverWithPermissions() throws Exception {
+ adoptShellPermissionIdentity();
+ final IAdbManager mAdbManager = IAdbManager.Stub.asInterface(
+ ServiceManager.getService(Context.ADB_SERVICE));
+ IntentFilter intentFilter =
+ new IntentFilter(AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION);
+ intentFilter.addAction(AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION);
+ intentFilter.addAction(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
+ assertEquals("Context does not have MANAGE_DEBUGGING permission.",
+ mContext.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING),
+ PackageManager.PERMISSION_GRANTED);
+ try {
+ mContext.registerReceiver(mReceiver, intentFilter);
+ mAdbManager.enablePairingByPairingCode();
+ if (!mAdbActionLatch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+ fail("Receiver did not receive adb intent action within the timeout duration");
+ }
+ } finally {
+ mContext.unregisterReceiver(mReceiver);
+ }
+ }
+
+ @Test
+ public void testBroadcastReceiverWithoutPermissions() throws Exception {
+ adoptShellPermissionIdentity();
+ final IAdbManager mAdbManager = IAdbManager.Stub.asInterface(
+ ServiceManager.getService(Context.ADB_SERVICE));
+ IntentFilter intentFilter =
+ new IntentFilter(AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION);
+ intentFilter.addAction(AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION);
+ intentFilter.addAction(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
+ mAdbManager.enablePairingByPairingCode();
+
+ dropShellPermissionIdentity();
+ assertEquals("Context has MANAGE_DEBUGGING permission.",
+ mContext.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING),
+ PackageManager.PERMISSION_DENIED);
+ try {
+ mContext.registerReceiver(mReceiver, intentFilter);
+
+ if (mAdbActionLatch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+ fail("Broadcast receiver received adb action intent without debug permissions");
+ }
+ } finally {
+ mContext.unregisterReceiver(mReceiver);
+ }
+ }
+
/**
* Runs an adb test with the provided configuration.
*
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
old mode 100755
new mode 100644
index 1a4cce4..c0ecbba
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -179,6 +179,8 @@
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
+import com.google.common.collect.ImmutableList;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -202,6 +204,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
@@ -245,6 +248,8 @@
@Mock
Resources mResources;
@Mock
+ ActivityManagerInternal mAmi;
+ @Mock
RankingHandler mRankingHandler;
@Mock
protected PackageManagerInternal mPackageManagerInternal;
@@ -418,7 +423,6 @@
DeviceIdleInternal deviceIdleInternal = mock(DeviceIdleInternal.class);
when(deviceIdleInternal.getNotificationWhitelistDuration()).thenReturn(3000L);
- ActivityManagerInternal activityManagerInternal = mock(ActivityManagerInternal.class);
LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal);
@@ -429,7 +433,7 @@
LocalServices.removeServiceForTest(DeviceIdleInternal.class);
LocalServices.addService(DeviceIdleInternal.class, deviceIdleInternal);
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
- LocalServices.addService(ActivityManagerInternal.class, activityManagerInternal);
+ LocalServices.addService(ActivityManagerInternal.class, mAmi);
mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
@@ -506,7 +510,7 @@
mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
mAppOpsManager, mUm, mHistoryManager, mStatsManager,
mock(TelephonyManager.class),
- mock(ActivityManagerInternal.class));
+ mAmi);
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
mService.setAudioManager(mAudioManager);
@@ -2473,7 +2477,8 @@
.thenReturn(associations);
NotificationChannelGroup ncg = new NotificationChannelGroup("a", "b/c");
mService.setPreferencesHelper(mPreferencesHelper);
- when(mPreferencesHelper.getNotificationChannelGroup(eq(ncg.getId()), eq(PKG), anyInt()))
+ when(mPreferencesHelper.getNotificationChannelGroupWithChannels(
+ eq(PKG), anyInt(), eq(ncg.getId()), anyBoolean()))
.thenReturn(ncg);
reset(mListeners);
mBinderService.deleteNotificationChannelGroup(PKG, ncg.getId());
@@ -2483,6 +2488,56 @@
}
@Test
+ public void testDeleteChannelGroupChecksForFgses() throws Exception {
+ List<String> associations = new ArrayList<>();
+ associations.add("a");
+ when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ .thenReturn(associations);
+ CountDownLatch latch = new CountDownLatch(2);
+ mService.createNotificationChannelGroup(
+ PKG, mUid, new NotificationChannelGroup("group", "group"), true, false);
+ new Thread(() -> {
+ NotificationChannel notificationChannel = new NotificationChannel("id", "id",
+ NotificationManager.IMPORTANCE_HIGH);
+ notificationChannel.setGroup("group");
+ ParceledListSlice<NotificationChannel> pls =
+ new ParceledListSlice(ImmutableList.of(notificationChannel));
+ try {
+ mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ latch.countDown();
+ }).start();
+ new Thread(() -> {
+ try {
+ synchronized (this) {
+ wait(5000);
+ }
+ mService.createNotificationChannelGroup(PKG, mUid,
+ new NotificationChannelGroup("new", "new group"), true, false);
+ NotificationChannel notificationChannel =
+ new NotificationChannel("id", "id", NotificationManager.IMPORTANCE_HIGH);
+ notificationChannel.setGroup("new");
+ ParceledListSlice<NotificationChannel> pls =
+ new ParceledListSlice(ImmutableList.of(notificationChannel));
+ try {
+ mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
+ mBinderService.deleteNotificationChannelGroup(PKG, "group");
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ latch.countDown();
+ }).start();
+
+ latch.await();
+ verify(mAmi).hasForegroundServiceNotification(anyString(), anyInt(), anyString());
+ }
+
+ @Test
public void testUpdateNotificationChannelFromPrivilegedListener_success() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 6aeb40d..06cfbea 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -2133,6 +2133,19 @@
}
@Test
+ public void testIsGroupBlocked_appCannotCreateAsBlocked() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+ group.setBlocked(true);
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
+ assertFalse(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId()));
+
+ NotificationChannelGroup group3 = group.clone();
+ group3.setBlocked(false);
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group3, true);
+ assertFalse(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId()));
+ }
+
+ @Test
public void testIsGroup_appCannotResetBlock() throws Exception {
NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
@@ -3402,7 +3415,7 @@
public void testGetConversations_noDisabledGroups() {
NotificationChannelGroup group = new NotificationChannelGroup("a", "a");
group.setBlocked(true);
- mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true);
+ mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, false);
NotificationChannel parent = new NotificationChannel("parent", "p", 1);
mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index a5b6760..a54f263 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -339,6 +339,7 @@
if (userId == UserHandle.USER_SYSTEM) {
UsageStatsIdleService.scheduleUpdateMappingsJob(getContext());
}
+ final boolean deleteObsoleteData = shouldDeleteObsoleteData(UserHandle.of(userId));
synchronized (mLock) {
// Create a user unlocked event to report
final Event unlockEvent = new Event(USER_UNLOCKED, SystemClock.elapsedRealtime());
@@ -356,7 +357,7 @@
boolean needToFlush = !pendingEvents.isEmpty();
initializeUserUsageStatsServiceLocked(userId, System.currentTimeMillis(),
- installedPackages);
+ installedPackages, deleteObsoleteData);
mUserUnlockedStates.put(userId, true);
final UserUsageStatsService userService = getUserUsageStatsServiceLocked(userId);
if (userService == null) {
@@ -551,13 +552,13 @@
* when the user is initially unlocked.
*/
private void initializeUserUsageStatsServiceLocked(int userId, long currentTimeMillis,
- HashMap<String, Long> installedPackages) {
+ HashMap<String, Long> installedPackages, boolean deleteObsoleteData) {
final File usageStatsDir = new File(Environment.getDataSystemCeDirectory(userId),
"usagestats");
final UserUsageStatsService service = new UserUsageStatsService(getContext(), userId,
usageStatsDir, this);
try {
- service.init(currentTimeMillis, installedPackages);
+ service.init(currentTimeMillis, installedPackages, deleteObsoleteData);
mUserState.put(userId, service);
} catch (Exception e) {
if (mUserManager.isUserUnlocked(userId)) {
@@ -1029,6 +1030,10 @@
* Called by the Binder stub.
*/
private boolean updatePackageMappingsData() {
+ // don't update the mappings if a profile user is defined
+ if (!shouldDeleteObsoleteData(UserHandle.SYSTEM)) {
+ return true; // return true so job scheduler doesn't reschedule the job
+ }
// fetch the installed packages outside the lock so it doesn't block package manager.
final HashMap<String, Long> installedPkgs = getInstalledPackages(UserHandle.USER_SYSTEM);
synchronized (mLock) {
@@ -1154,6 +1159,13 @@
}
}
+ private boolean shouldDeleteObsoleteData(UserHandle userHandle) {
+ final DevicePolicyManagerInternal dpmInternal = getDpmInternal();
+ // If a profile owner is not defined for the given user, obsolete data should be deleted
+ return dpmInternal == null
+ || dpmInternal.getProfileOwnerOrDeviceOwnerSupervisionComponent(userHandle) == null;
+ }
+
private String buildFullToken(String packageName, String token) {
final StringBuilder sb = new StringBuilder(packageName.length() + token.length() + 1);
sb.append(packageName);
@@ -2324,8 +2336,12 @@
private class MyPackageMonitor extends PackageMonitor {
@Override
public void onPackageRemoved(String packageName, int uid) {
- mHandler.obtainMessage(MSG_PACKAGE_REMOVED, getChangingUserId(), 0, packageName)
- .sendToTarget();
+ final int changingUserId = getChangingUserId();
+ // Only remove the package's data if a profile owner is not defined for the user
+ if (shouldDeleteObsoleteData(UserHandle.of(changingUserId))) {
+ mHandler.obtainMessage(MSG_PACKAGE_REMOVED, changingUserId, 0, packageName)
+ .sendToTarget();
+ }
super.onPackageRemoved(packageName, uid);
}
}
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 26de11a..0a5adc8 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -115,8 +115,9 @@
mSystemTimeSnapshot = System.currentTimeMillis();
}
- void init(final long currentTimeMillis, HashMap<String, Long> installedPackages) {
- readPackageMappingsLocked(installedPackages);
+ void init(final long currentTimeMillis, HashMap<String, Long> installedPackages,
+ boolean deleteObsoleteData) {
+ readPackageMappingsLocked(installedPackages, deleteObsoleteData);
mDatabase.init(currentTimeMillis);
if (mDatabase.wasUpgradePerformed()) {
mDatabase.prunePackagesDataOnUpgrade(installedPackages);
@@ -180,12 +181,13 @@
return mDatabase.onPackageRemoved(packageName, timeRemoved);
}
- private void readPackageMappingsLocked(HashMap<String, Long> installedPackages) {
+ private void readPackageMappingsLocked(HashMap<String, Long> installedPackages,
+ boolean deleteObsoleteData) {
mDatabase.readMappingsLocked();
// Package mappings for the system user are updated after 24 hours via a job scheduled by
// UsageStatsIdleService to ensure restored data is not lost on first boot. Additionally,
// this makes user service initialization a little quicker on subsequent boots.
- if (mUserId != UserHandle.USER_SYSTEM) {
+ if (mUserId != UserHandle.USER_SYSTEM && deleteObsoleteData) {
updatePackageMappingsLocked(installedPackages);
}
}