Merge "Add credential manager FEATURE_ constant to Package Manager Test: Manual. Built locally Bug: 248609653"
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index c12f5b4..56d91b2 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -41,7 +41,6 @@
":libtombstone_proto-src",
"core/proto/**/*.proto",
"libs/incident/**/*.proto",
- ":service-permission-streaming-proto-sources",
],
output_extension: "srcjar",
}
@@ -72,7 +71,6 @@
":libstats_atom_message_protos",
"core/proto/**/*.proto",
"libs/incident/**/*.proto",
- ":service-permission-streaming-proto-sources",
],
output_extension: "proto.h",
@@ -91,7 +89,6 @@
"cmds/statsd/src/**/*.proto",
"core/proto/**/*.proto",
"libs/incident/proto/**/*.proto",
- ":service-permission-streaming-proto-sources",
],
proto: {
include_dirs: [
@@ -126,7 +123,6 @@
":libstats_atom_message_protos",
"core/proto/**/*.proto",
"libs/incident/proto/android/os/**/*.proto",
- ":service-permission-streaming-proto-sources",
],
// Protos have lots of MissingOverride and similar.
errorprone: {
@@ -148,7 +144,6 @@
":libstats_atom_message_protos",
"core/proto/**/*.proto",
"libs/incident/proto/android/os/**/*.proto",
- ":service-permission-streaming-proto-sources",
],
exclude_srcs: [
"core/proto/android/privacy.proto",
@@ -184,7 +179,6 @@
":libstats_atom_enum_protos",
":libstats_atom_message_protos",
"core/proto/**/*.proto",
- ":service-permission-streaming-proto-sources",
],
}
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 5b61f9a..22f70ce 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -57,6 +57,8 @@
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityManagerInternal;
@@ -289,6 +291,7 @@
final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
IBinder.DeathRecipient mListenerDeathRecipient;
Intent mTimeTickIntent;
+ Bundle mTimeTickOptions;
IAlarmListener mTimeTickTrigger;
PendingIntent mDateChangeSender;
boolean mInteractive = true;
@@ -1909,7 +1912,9 @@
Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
-
+ mTimeTickOptions = BroadcastOptions
+ .makeRemovingMatchingFilter(new IntentFilter(Intent.ACTION_TIME_TICK))
+ .toBundle();
mTimeTickTrigger = new IAlarmListener.Stub() {
@Override
public void doAlarm(final IAlarmCompleteListener callback) throws RemoteException {
@@ -1921,8 +1926,8 @@
// takes care of this automatically, but we're using the direct internal
// interface here rather than that client-side wrapper infrastructure.
mHandler.post(() -> {
- getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL);
-
+ getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL, null,
+ mTimeTickOptions);
try {
callback.alarmComplete(this);
} catch (RemoteException e) { /* local method call */ }
diff --git a/core/api/current.txt b/core/api/current.txt
index 83ada14..65c1f46 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -45195,6 +45195,7 @@
method public final int getLineAscent(int);
method public final int getLineBaseline(int);
method public final int getLineBottom(int);
+ method public int getLineBottom(int, boolean);
method public int getLineBounds(int, android.graphics.Rect);
method public abstract boolean getLineContainsTab(int);
method public abstract int getLineCount();
@@ -53317,6 +53318,7 @@
method public default void performHandwritingGesture(@NonNull android.view.inputmethod.HandwritingGesture, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.IntConsumer);
method public boolean performPrivateCommand(String, android.os.Bundle);
method public default boolean performSpellCheck();
+ method public default boolean replaceText(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute);
method public boolean reportFullscreenMode(boolean);
method public boolean requestCursorUpdates(int);
method public default boolean requestCursorUpdates(int, int);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index dfef279..8ae16df6 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4667,7 +4667,7 @@
* @hide
*/
public static void broadcastStickyIntent(Intent intent, int userId) {
- broadcastStickyIntent(intent, AppOpsManager.OP_NONE, userId);
+ broadcastStickyIntent(intent, AppOpsManager.OP_NONE, null, userId);
}
/**
@@ -4676,11 +4676,20 @@
* @hide
*/
public static void broadcastStickyIntent(Intent intent, int appOp, int userId) {
+ broadcastStickyIntent(intent, appOp, null, userId);
+ }
+
+ /**
+ * Convenience for sending a sticky broadcast. For internal use only.
+ *
+ * @hide
+ */
+ public static void broadcastStickyIntent(Intent intent, int appOp, Bundle options, int userId) {
try {
getService().broadcastIntentWithFeature(
null, null, intent, null, null, Activity.RESULT_OK, null, null,
null /*requiredPermissions*/, null /*excludedPermissions*/,
- null /*excludedPackages*/, appOp, null, false, true, userId);
+ null /*excludedPackages*/, appOp, options, false, true, userId);
} catch (RemoteException ex) {
}
}
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 419b8e1..626b7d3 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -30,6 +30,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityPresentationInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PermissionMethod;
import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.Bundle;
@@ -292,6 +293,7 @@
boolean allowAll, int allowMode, String name, String callerPackage);
/** Checks if the calling binder pid as the permission. */
+ @PermissionMethod
public abstract void enforceCallingPermission(String permission, String func);
/** Returns the current user id. */
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index aa5fa5b..c2df802 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -27,12 +27,15 @@
import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledSince;
import android.content.Intent;
+import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerExemptionManager;
import android.os.PowerExemptionManager.ReasonCode;
import android.os.PowerExemptionManager.TempAllowListType;
+import java.util.Objects;
+
/**
* Helper class for building an options Bundle that can be used with
* {@link android.content.Context#sendBroadcast(android.content.Intent)
@@ -55,6 +58,7 @@
private boolean mRequireCompatChangeEnabled = true;
private boolean mIsAlarmBroadcast = false;
private long mIdForResponseEvent;
+ private @Nullable IntentFilter mRemoveMatchingFilter;
/**
* Change ID which is invalid.
@@ -180,11 +184,25 @@
private static final String KEY_ID_FOR_RESPONSE_EVENT =
"android:broadcast.idForResponseEvent";
+ /**
+ * Corresponds to {@link #setRemoveMatchingFilter}.
+ */
+ private static final String KEY_REMOVE_MATCHING_FILTER =
+ "android:broadcast.removeMatchingFilter";
+
public static BroadcastOptions makeBasic() {
BroadcastOptions opts = new BroadcastOptions();
return opts;
}
+ /** {@hide} */
+ public static @NonNull BroadcastOptions makeRemovingMatchingFilter(
+ @NonNull IntentFilter removeMatchingFilter) {
+ BroadcastOptions opts = new BroadcastOptions();
+ opts.setRemoveMatchingFilter(removeMatchingFilter);
+ return opts;
+ }
+
private BroadcastOptions() {
super();
resetTemporaryAppAllowlist();
@@ -216,6 +234,8 @@
mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true);
mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false);
+ mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER,
+ IntentFilter.class);
}
/**
@@ -596,6 +616,29 @@
}
/**
+ * When enqueuing this broadcast, remove all pending broadcasts previously
+ * sent by this app which match the given filter.
+ * <p>
+ * For example, sending {@link Intent#ACTION_SCREEN_ON} would typically want
+ * to remove any pending {@link Intent#ACTION_SCREEN_OFF} broadcasts.
+ *
+ * @hide
+ */
+ public void setRemoveMatchingFilter(@NonNull IntentFilter removeMatchingFilter) {
+ mRemoveMatchingFilter = Objects.requireNonNull(removeMatchingFilter);
+ }
+
+ /** @hide */
+ public void clearRemoveMatchingFilter() {
+ mRemoveMatchingFilter = null;
+ }
+
+ /** @hide */
+ public @Nullable IntentFilter getRemoveMatchingFilter() {
+ return mRemoveMatchingFilter;
+ }
+
+ /**
* Returns the created options as a Bundle, which can be passed to
* {@link android.content.Context#sendBroadcast(android.content.Intent)
* Context.sendBroadcast(Intent)} and related methods.
@@ -640,6 +683,9 @@
if (mIdForResponseEvent != 0) {
b.putLong(KEY_ID_FOR_RESPONSE_EVENT, mIdForResponseEvent);
}
+ if (mRemoveMatchingFilter != null) {
+ b.putParcelable(KEY_REMOVE_MATCHING_FILTER, mRemoveMatchingFilter);
+ }
return b.isEmpty() ? null : b;
}
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 97da2da..430b52c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -51,6 +51,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PermissionMethod;
import android.content.res.AssetManager;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -6066,6 +6067,7 @@
*/
@CheckResult(suggest="#enforcePermission(String,int,int,String)")
@PackageManager.PermissionResult
+ @PermissionMethod
public abstract int checkPermission(@NonNull String permission, int pid, int uid);
/** @hide */
@@ -6098,6 +6100,7 @@
*/
@CheckResult(suggest="#enforceCallingPermission(String,String)")
@PackageManager.PermissionResult
+ @PermissionMethod
public abstract int checkCallingPermission(@NonNull String permission);
/**
@@ -6118,6 +6121,7 @@
*/
@CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)")
@PackageManager.PermissionResult
+ @PermissionMethod
public abstract int checkCallingOrSelfPermission(@NonNull String permission);
/**
@@ -6146,6 +6150,7 @@
*
* @see #checkPermission(String, int, int)
*/
+ @PermissionMethod
public abstract void enforcePermission(
@NonNull String permission, int pid, int uid, @Nullable String message);
@@ -6167,6 +6172,7 @@
*
* @see #checkCallingPermission(String)
*/
+ @PermissionMethod
public abstract void enforceCallingPermission(
@NonNull String permission, @Nullable String message);
@@ -6183,6 +6189,7 @@
*
* @see #checkCallingOrSelfPermission(String)
*/
+ @PermissionMethod
public abstract void enforceCallingOrSelfPermission(
@NonNull String permission, @Nullable String message);
diff --git a/core/java/android/content/pm/PermissionMethod.java b/core/java/android/content/pm/PermissionMethod.java
new file mode 100644
index 0000000..021b2e1
--- /dev/null
+++ b/core/java/android/content/pm/PermissionMethod.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 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.content.pm;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Documents that the subject method's job is to look
+ * up whether the provided or calling uid/pid has the requested permission.
+ *
+ * Methods should either return `void`, but potentially throw {@link SecurityException},
+ * or return {@link android.content.pm.PackageManager.PermissionResult} `int`.
+ *
+ * @hide
+ */
+@Retention(CLASS)
+@Target({METHOD})
+public @interface PermissionMethod {}
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 05daf63..a87b133 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -147,119 +147,146 @@
@MainThread
@Override
public void executeMessage(Message msg) {
- InputMethod inputMethod = mInputMethod.get();
- // Need a valid reference to the inputMethod for everything except a dump.
- if (inputMethod == null && msg.what != DO_DUMP) {
- Log.w(TAG, "Input method reference was null, ignoring message: " + msg.what);
- return;
- }
-
+ final InputMethod inputMethod = mInputMethod.get();
+ final InputMethodServiceInternal target = mTarget.get();
switch (msg.what) {
case DO_DUMP: {
- InputMethodServiceInternal target = mTarget.get();
- if (target == null) {
- return;
- }
SomeArgs args = (SomeArgs)msg.obj;
- try {
- target.dump((FileDescriptor) args.arg1,
- (PrintWriter) args.arg2, (String[]) args.arg3);
- } catch (RuntimeException e) {
- ((PrintWriter)args.arg2).println("Exception: " + e);
- }
- synchronized (args.arg4) {
- ((CountDownLatch)args.arg4).countDown();
+ if (isValid(inputMethod, target, "DO_DUMP")) {
+ final FileDescriptor fd = (FileDescriptor) args.arg1;
+ final PrintWriter fout = (PrintWriter) args.arg2;
+ final String[] dumpArgs = (String[]) args.arg3;
+ final CountDownLatch latch = (CountDownLatch) args.arg4;
+ try {
+ target.dump(fd, fout, dumpArgs);
+ } catch (RuntimeException e) {
+ fout.println("Exception: " + e);
+ } finally {
+ latch.countDown();
+ }
}
args.recycle();
return;
}
case DO_INITIALIZE_INTERNAL:
- inputMethod.initializeInternal((IInputMethod.InitParams) msg.obj);
+ if (isValid(inputMethod, target, "DO_INITIALIZE_INTERNAL")) {
+ inputMethod.initializeInternal((IInputMethod.InitParams) msg.obj);
+ }
return;
case DO_SET_INPUT_CONTEXT: {
- inputMethod.bindInput((InputBinding)msg.obj);
+ if (isValid(inputMethod, target, "DO_SET_INPUT_CONTEXT")) {
+ inputMethod.bindInput((InputBinding) msg.obj);
+ }
return;
}
case DO_UNSET_INPUT_CONTEXT:
- inputMethod.unbindInput();
+ if (isValid(inputMethod, target, "DO_UNSET_INPUT_CONTEXT")) {
+ inputMethod.unbindInput();
+ }
return;
case DO_START_INPUT: {
final SomeArgs args = (SomeArgs) msg.obj;
- final InputConnection inputConnection = (InputConnection) args.arg1;
- final IInputMethod.StartInputParams params =
- (IInputMethod.StartInputParams) args.arg2;
- inputMethod.dispatchStartInput(inputConnection, params);
+ if (isValid(inputMethod, target, "DO_START_INPUT")) {
+ final InputConnection inputConnection = (InputConnection) args.arg1;
+ final IInputMethod.StartInputParams params =
+ (IInputMethod.StartInputParams) args.arg2;
+ inputMethod.dispatchStartInput(inputConnection, params);
+ }
args.recycle();
return;
}
case DO_ON_NAV_BUTTON_FLAGS_CHANGED:
- inputMethod.onNavButtonFlagsChanged(msg.arg1);
+ if (isValid(inputMethod, target, "DO_ON_NAV_BUTTON_FLAGS_CHANGED")) {
+ inputMethod.onNavButtonFlagsChanged(msg.arg1);
+ }
return;
case DO_CREATE_SESSION: {
SomeArgs args = (SomeArgs)msg.obj;
- inputMethod.createSession(new InputMethodSessionCallbackWrapper(
- mContext, (InputChannel) args.arg1,
- (IInputMethodSessionCallback) args.arg2));
+ if (isValid(inputMethod, target, "DO_CREATE_SESSION")) {
+ inputMethod.createSession(new InputMethodSessionCallbackWrapper(
+ mContext, (InputChannel) args.arg1,
+ (IInputMethodSessionCallback) args.arg2));
+ }
args.recycle();
return;
}
case DO_SET_SESSION_ENABLED:
- inputMethod.setSessionEnabled((InputMethodSession)msg.obj,
- msg.arg1 != 0);
+ if (isValid(inputMethod, target, "DO_SET_SESSION_ENABLED")) {
+ inputMethod.setSessionEnabled((InputMethodSession) msg.obj, msg.arg1 != 0);
+ }
return;
case DO_SHOW_SOFT_INPUT: {
final SomeArgs args = (SomeArgs)msg.obj;
- inputMethod.showSoftInputWithToken(
- msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1);
+ if (isValid(inputMethod, target, "DO_SHOW_SOFT_INPUT")) {
+ inputMethod.showSoftInputWithToken(
+ msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1);
+ }
args.recycle();
return;
}
case DO_HIDE_SOFT_INPUT: {
final SomeArgs args = (SomeArgs) msg.obj;
- inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
- (IBinder) args.arg1);
+ if (isValid(inputMethod, target, "DO_HIDE_SOFT_INPUT")) {
+ inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
+ (IBinder) args.arg1);
+ }
args.recycle();
return;
}
case DO_CHANGE_INPUTMETHOD_SUBTYPE:
- inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
+ if (isValid(inputMethod, target, "DO_CHANGE_INPUTMETHOD_SUBTYPE")) {
+ inputMethod.changeInputMethodSubtype((InputMethodSubtype) msg.obj);
+ }
return;
case DO_CREATE_INLINE_SUGGESTIONS_REQUEST: {
final SomeArgs args = (SomeArgs) msg.obj;
- inputMethod.onCreateInlineSuggestionsRequest(
- (InlineSuggestionsRequestInfo) args.arg1,
- (IInlineSuggestionsRequestCallback) args.arg2);
+ if (isValid(inputMethod, target, "DO_CREATE_INLINE_SUGGESTIONS_REQUEST")) {
+ inputMethod.onCreateInlineSuggestionsRequest(
+ (InlineSuggestionsRequestInfo) args.arg1,
+ (IInlineSuggestionsRequestCallback) args.arg2);
+ }
args.recycle();
return;
}
case DO_CAN_START_STYLUS_HANDWRITING: {
- inputMethod.canStartStylusHandwriting(msg.arg1);
+ if (isValid(inputMethod, target, "DO_CAN_START_STYLUS_HANDWRITING")) {
+ inputMethod.canStartStylusHandwriting(msg.arg1);
+ }
return;
}
case DO_UPDATE_TOOL_TYPE: {
- inputMethod.updateEditorToolType(msg.arg1);
+ if (isValid(inputMethod, target, "DO_UPDATE_TOOL_TYPE")) {
+ inputMethod.updateEditorToolType(msg.arg1);
+ }
return;
}
case DO_START_STYLUS_HANDWRITING: {
final SomeArgs args = (SomeArgs) msg.obj;
- inputMethod.startStylusHandwriting(msg.arg1, (InputChannel) args.arg1,
- (List<MotionEvent>) args.arg2);
+ if (isValid(inputMethod, target, "DO_START_STYLUS_HANDWRITING")) {
+ inputMethod.startStylusHandwriting(msg.arg1, (InputChannel) args.arg1,
+ (List<MotionEvent>) args.arg2);
+ }
args.recycle();
return;
}
case DO_INIT_INK_WINDOW: {
- inputMethod.initInkWindow();
+ if (isValid(inputMethod, target, "DO_INIT_INK_WINDOW")) {
+ inputMethod.initInkWindow();
+ }
return;
}
case DO_FINISH_STYLUS_HANDWRITING: {
- inputMethod.finishStylusHandwriting();
+ if (isValid(inputMethod, target, "DO_FINISH_STYLUS_HANDWRITING")) {
+ inputMethod.finishStylusHandwriting();
+ }
return;
}
case DO_REMOVE_STYLUS_HANDWRITING_WINDOW: {
- inputMethod.removeStylusHandwritingWindow();
+ if (isValid(inputMethod, target, "DO_REMOVE_STYLUS_HANDWRITING_WINDOW")) {
+ inputMethod.removeStylusHandwritingWindow();
+ }
return;
}
-
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
@@ -445,4 +472,15 @@
public void removeStylusHandwritingWindow() {
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_STYLUS_HANDWRITING_WINDOW));
}
+
+ private static boolean isValid(InputMethod inputMethod, InputMethodServiceInternal target,
+ String msg) {
+ if (inputMethod != null && target != null && !target.isServiceDestroyed()) {
+ return true;
+ } else {
+ Log.w(TAG, "Ignoring " + msg + ", InputMethod:" + inputMethod
+ + ", InputMethodServiceInternal:" + target);
+ return false;
+ }
+ }
}
diff --git a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
index 6f758de..891da24 100644
--- a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
+++ b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
@@ -775,4 +775,33 @@
return false;
}
}
+
+ /**
+ * Invokes {@link IRemoteInputConnection#replaceText(InputConnectionCommandHeader, int, int,
+ * CharSequence, TextAttribute)}.
+ *
+ * @param start the character index where the replacement should start.
+ * @param end the character index where the replacement should end.
+ * @param newCursorPosition the new cursor position around the text. If > 0, this is relative to
+ * the end of the text - 1; if <= 0, this is relative to the start of the text. So a value
+ * of 1 will always advance you to the position after the full text being inserted. Note
+ * that this means you can't position the cursor within the text.
+ * @param text the text to replace. This may include styles.
+ * @param textAttribute The extra information about the text. This value may be null.
+ */
+ @AnyThread
+ public boolean replaceText(
+ int start,
+ int end,
+ @NonNull CharSequence text,
+ int newCursorPosition,
+ @Nullable TextAttribute textAttribute) {
+ try {
+ mConnection.replaceText(
+ createHeader(), start, end, text, newCursorPosition, textAttribute);
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 7436601..8b3451e 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -700,11 +700,6 @@
@MainThread
@Override
public final void initializeInternal(@NonNull IInputMethod.InitParams params) {
- if (mDestroyed) {
- Log.i(TAG, "The InputMethodService has already onDestroyed()."
- + "Ignore the initialization.");
- return;
- }
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal");
mConfigTracker.onInitialize(params.configChanges);
mPrivOps.set(params.privilegedOperations);
@@ -3938,6 +3933,14 @@
public void triggerServiceDump(String where, @Nullable byte[] icProto) {
ImeTracing.getInstance().triggerServiceDump(where, mDumper, icProto);
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isServiceDestroyed() {
+ return mDestroyed;
+ }
};
}
diff --git a/core/java/android/inputmethodservice/InputMethodServiceInternal.java b/core/java/android/inputmethodservice/InputMethodServiceInternal.java
index f44f49d..c6612f6 100644
--- a/core/java/android/inputmethodservice/InputMethodServiceInternal.java
+++ b/core/java/android/inputmethodservice/InputMethodServiceInternal.java
@@ -85,4 +85,11 @@
*/
default void triggerServiceDump(@NonNull String where, @Nullable byte[] icProto) {
}
+
+ /**
+ * @return {@code true} if {@link InputMethodService} is destroyed.
+ */
+ default boolean isServiceDestroyed() {
+ return false;
+ };
}
diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java
index 694293c..2b5f14d 100644
--- a/core/java/android/inputmethodservice/RemoteInputConnection.java
+++ b/core/java/android/inputmethodservice/RemoteInputConnection.java
@@ -498,6 +498,17 @@
return mInvoker.setImeConsumesInput(imeConsumesInput);
}
+ /** See {@link InputConnection#replaceText(int, int, CharSequence, int, TextAttribute)}. */
+ @AnyThread
+ public boolean replaceText(
+ int start,
+ int end,
+ @NonNull CharSequence text,
+ int newCursorPosition,
+ @Nullable TextAttribute textAttribute) {
+ return mInvoker.replaceText(start, end, text, newCursorPosition, textAttribute);
+ }
+
@AnyThread
@Override
public String toString() {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 5c809a1..607d1e1 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2869,10 +2869,10 @@
* It includes:
*
* <ol>
- * <li>The current foreground user in the main display.
- * <li>Current background users in secondary displays (for example, passenger users on
- * automotive, using the display associated with their seats).
- * <li>Profile users (in the running / started state) of other visible users.
+ * <li>The current foreground user.
+ * <li>(Running) profiles of the current foreground user.
+ * <li>Background users assigned to secondary displays (for example, passenger users on
+ * automotive builds, using the display associated with their seats).
* </ol>
*
* @return whether the user is visible at the moment, as defined above.
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index b5f7c54..dbb41f4 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1841,7 +1841,7 @@
// Find the first line whose vertical center is below the top of the area.
int startLine = getLineForVertical((int) area.top);
int startLineTop = getLineTop(startLine);
- int startLineBottom = getLineBottomWithoutSpacing(startLine);
+ int startLineBottom = getLineBottom(startLine, /* includeLineSpacing= */ false);
if (area.top > (startLineTop + startLineBottom) / 2f) {
startLine++;
if (startLine >= getLineCount()) {
@@ -1854,7 +1854,7 @@
// Find the last line whose vertical center is above the bottom of the area.
int endLine = getLineForVertical((int) area.bottom);
int endLineTop = getLineTop(endLine);
- int endLineBottom = getLineBottomWithoutSpacing(endLine);
+ int endLineBottom = getLineBottom(endLine, /* includeLineSpacing= */ false);
if (area.bottom < (endLineTop + endLineBottom) / 2f) {
endLine--;
}
@@ -2229,17 +2229,21 @@
* Return the vertical position of the bottom of the specified line.
*/
public final int getLineBottom(int line) {
- return getLineTop(line + 1);
+ return getLineBottom(line, /* includeLineSpacing= */ true);
}
/**
- * Return the vertical position of the bottom of the specified line without the line spacing
- * added.
+ * Return the vertical position of the bottom of the specified line.
*
- * @hide
+ * @param line index of the line
+ * @param includeLineSpacing whether to include the line spacing
*/
- public final int getLineBottomWithoutSpacing(int line) {
- return getLineTop(line + 1) - getLineExtra(line);
+ public int getLineBottom(int line, boolean includeLineSpacing) {
+ if (includeLineSpacing) {
+ return getLineTop(line + 1);
+ } else {
+ return getLineTop(line + 1) - getLineExtra(line);
+ }
}
/**
@@ -2394,7 +2398,7 @@
int line = getLineForOffset(point);
int top = getLineTop(line);
- int bottom = getLineBottomWithoutSpacing(line);
+ int bottom = getLineBottom(line, /* includeLineSpacing= */ false);
boolean clamped = shouldClampCursor(line);
float h1 = getPrimaryHorizontal(point, clamped) - 0.5f;
@@ -2530,7 +2534,7 @@
final int endline = getLineForOffset(end);
int top = getLineTop(startline);
- int bottom = getLineBottomWithoutSpacing(endline);
+ int bottom = getLineBottom(endline, /* includeLineSpacing= */ false);
if (startline == endline) {
addSelection(startline, start, end, top, bottom, consumer);
@@ -2559,7 +2563,7 @@
}
top = getLineTop(endline);
- bottom = getLineBottomWithoutSpacing(endline);
+ bottom = getLineBottom(endline, /* includeLineSpacing= */ false);
addSelection(endline, getLineStart(endline), end, top, bottom, consumer);
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 51e3665..596e491 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -93,7 +93,9 @@
private static final String ELLIPSIS_NORMAL = "\u2026"; // HORIZONTAL ELLIPSIS (…)
private static final String ELLIPSIS_TWO_DOTS = "\u2025"; // TWO DOT LEADER (‥)
- private static final int LINE_FEED_CODE_POINT = 10;
+ /** @hide */
+ public static final int LINE_FEED_CODE_POINT = 10;
+
private static final int NBSP_CODE_POINT = 160;
/**
@@ -2335,11 +2337,29 @@
|| codePoint == LINE_FEED_CODE_POINT;
}
- private static boolean isWhiteSpace(int codePoint) {
+ /** @hide */
+ public static boolean isWhitespace(int codePoint) {
return Character.isWhitespace(codePoint) || codePoint == NBSP_CODE_POINT;
}
/** @hide */
+ public static boolean isWhitespaceExceptNewline(int codePoint) {
+ return isWhitespace(codePoint) && !isNewline(codePoint);
+ }
+
+ /** @hide */
+ public static boolean isPunctuation(int codePoint) {
+ int type = Character.getType(codePoint);
+ return type == Character.CONNECTOR_PUNCTUATION
+ || type == Character.DASH_PUNCTUATION
+ || type == Character.END_PUNCTUATION
+ || type == Character.FINAL_QUOTE_PUNCTUATION
+ || type == Character.INITIAL_QUOTE_PUNCTUATION
+ || type == Character.OTHER_PUNCTUATION
+ || type == Character.START_PUNCTUATION;
+ }
+
+ /** @hide */
@Nullable
public static String withoutPrefix(@Nullable String prefix, @Nullable String str) {
if (prefix == null || str == null) return str;
@@ -2430,7 +2450,7 @@
gettingCleaned.removeRange(offset, offset + codePointLen);
} else if (type == Character.CONTROL && !isNewline) {
gettingCleaned.removeRange(offset, offset + codePointLen);
- } else if (trim && !isWhiteSpace(codePoint)) {
+ } else if (trim && !isWhitespace(codePoint)) {
// This is only executed if the code point is not removed
if (firstNonWhiteSpace == -1) {
firstNonWhiteSpace = offset;
diff --git a/core/java/android/text/method/WordIterator.java b/core/java/android/text/method/WordIterator.java
index f427e1b..6d18d2c 100644
--- a/core/java/android/text/method/WordIterator.java
+++ b/core/java/android/text/method/WordIterator.java
@@ -24,6 +24,7 @@
import android.os.Build;
import android.text.CharSequenceCharacterIterator;
import android.text.Selection;
+import android.text.TextUtils;
import java.util.Locale;
@@ -275,9 +276,9 @@
}
/**
- * If <code>offset</code> is within a group of punctuation as defined
- * by {@link #isPunctuation(int)}, returns the index of the first character
- * of that group, otherwise returns BreakIterator.DONE.
+ * If <code>offset</code> is within a group of punctuation as defined by {@link
+ * TextUtils#isPunctuation(int)}, returns the index of the first character of that group,
+ * otherwise returns BreakIterator.DONE.
*
* @param offset the offset to search from.
*/
@@ -292,9 +293,9 @@
}
/**
- * If <code>offset</code> is within a group of punctuation as defined
- * by {@link #isPunctuation(int)}, returns the index of the last character
- * of that group plus one, otherwise returns BreakIterator.DONE.
+ * If <code>offset</code> is within a group of punctuation as defined by {@link
+ * TextUtils#isPunctuation(int)}, returns the index of the last character of that group plus
+ * one, otherwise returns BreakIterator.DONE.
*
* @param offset the offset to search from.
*/
@@ -309,8 +310,8 @@
}
/**
- * Indicates if the provided offset is after a punctuation character
- * as defined by {@link #isPunctuation(int)}.
+ * Indicates if the provided offset is after a punctuation character as defined by {@link
+ * TextUtils#isPunctuation(int)}.
*
* @param offset the offset to check from.
* @return Whether the offset is after a punctuation character.
@@ -319,14 +320,14 @@
public boolean isAfterPunctuation(int offset) {
if (mStart < offset && offset <= mEnd) {
final int codePoint = Character.codePointBefore(mCharSeq, offset);
- return isPunctuation(codePoint);
+ return TextUtils.isPunctuation(codePoint);
}
return false;
}
/**
- * Indicates if the provided offset is at a punctuation character
- * as defined by {@link #isPunctuation(int)}.
+ * Indicates if the provided offset is at a punctuation character as defined by {@link
+ * TextUtils#isPunctuation(int)}.
*
* @param offset the offset to check from.
* @return Whether the offset is at a punctuation character.
@@ -335,7 +336,7 @@
public boolean isOnPunctuation(int offset) {
if (mStart <= offset && offset < mEnd) {
final int codePoint = Character.codePointAt(mCharSeq, offset);
- return isPunctuation(codePoint);
+ return TextUtils.isPunctuation(codePoint);
}
return false;
}
@@ -369,17 +370,6 @@
return !isOnPunctuation(offset) && isAfterPunctuation(offset);
}
- private static boolean isPunctuation(int cp) {
- final int type = Character.getType(cp);
- return (type == Character.CONNECTOR_PUNCTUATION
- || type == Character.DASH_PUNCTUATION
- || type == Character.END_PUNCTUATION
- || type == Character.FINAL_QUOTE_PUNCTUATION
- || type == Character.INITIAL_QUOTE_PUNCTUATION
- || type == Character.OTHER_PUNCTUATION
- || type == Character.START_PUNCTUATION);
- }
-
private boolean isAfterLetterOrDigit(int offset) {
if (mStart < offset && offset <= mEnd) {
final int codePoint = Character.codePointBefore(mCharSeq, offset);
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 5832527..c8c941a 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -22,6 +22,7 @@
import static android.view.InsetsSourceProto.VISIBLE_FRAME;
import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_IME;
+import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -148,7 +149,7 @@
// During drag-move and drag-resizing, the caption insets position may not get updated
// before the app frame get updated. To layout the app content correctly during drag events,
// we always return the insets with the corresponding height covering the top.
- if (getType() == ITYPE_CAPTION_BAR) {
+ if (!CAPTION_ON_SHELL && getType() == ITYPE_CAPTION_BAR) {
return Insets.of(0, frame.height(), 0, 0);
}
// Checks for whether there is shared edge with insets for 0-width/height window.
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 90384b5..c32ca9e 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -528,7 +528,12 @@
@Override
@UiThread
void flush(@FlushReason int reason) {
- if (mEvents == null) return;
+ if (mEvents == null || mEvents.size() == 0) {
+ if (sVerbose) {
+ Log.v(TAG, "Don't flush for empty event buffer.");
+ }
+ return;
+ }
if (mDisabled.get()) {
Log.e(TAG, "handleForceFlush(" + getDebugState(reason) + "): should not be when "
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index a72f0d5..3733c3f 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -897,8 +897,43 @@
}
}
- private void replaceText(CharSequence text, int newCursorPosition,
- boolean composing) {
+ @Override
+ public boolean replaceText(
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ @NonNull CharSequence text,
+ int newCursorPosition,
+ @Nullable TextAttribute textAttribute) {
+ Preconditions.checkArgumentNonnegative(start);
+ Preconditions.checkArgumentNonnegative(end);
+
+ if (DEBUG) {
+ Log.v(
+ TAG,
+ "replaceText " + start + ", " + end + ", " + text + ", " + newCursorPosition);
+ }
+
+ final Editable content = getEditable();
+ if (content == null) {
+ return false;
+ }
+ beginBatchEdit();
+ removeComposingSpans(content);
+
+ int len = content.length();
+ start = Math.min(start, len);
+ end = Math.min(end, len);
+ if (end < start) {
+ int tmp = start;
+ start = end;
+ end = tmp;
+ }
+ replaceTextInternal(start, end, text, newCursorPosition, /*composing=*/ false);
+ endBatchEdit();
+ return true;
+ }
+
+ private void replaceText(CharSequence text, int newCursorPosition, boolean composing) {
final Editable content = getEditable();
if (content == null) {
return;
@@ -931,6 +966,16 @@
b = tmp;
}
}
+ replaceTextInternal(a, b, text, newCursorPosition, composing);
+ endBatchEdit();
+ }
+
+ private void replaceTextInternal(
+ int a, int b, CharSequence text, int newCursorPosition, boolean composing) {
+ final Editable content = getEditable();
+ if (content == null) {
+ return;
+ }
if (composing) {
Spannable sp = null;
@@ -974,7 +1019,6 @@
if (newCursorPosition < 0) newCursorPosition = 0;
if (newCursorPosition > content.length()) newCursorPosition = content.length();
Selection.setSelection(content, newCursorPosition);
-
content.replace(a, b, text);
if (DEBUG) {
@@ -982,8 +1026,6 @@
lp.println("Final text:");
TextUtils.dumpSpans(content, lp, " ");
}
-
- endBatchEdit();
}
/**
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 2f834c9..7d268a9 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -1329,4 +1329,44 @@
// existing APIs.
return null;
}
+
+ /**
+ * Replace the specific range in the editor with suggested text.
+ *
+ * <p>This method finishes whatever composing text is currently active and leaves the text
+ * as-it, replaces the specific range of text with the passed CharSequence, and then moves the
+ * cursor according to {@code newCursorPosition}. This behaves like calling {@link
+ * #finishComposingText()}, {@link #setSelection(int, int) setSelection(start, end)}, and then
+ * {@link #commitText(CharSequence, int, TextAttribute) commitText(text, newCursorPosition,
+ * textAttribute)}.
+ *
+ * <p>Similar to {@link #setSelection(int, int)}, the order of start and end is not important.
+ * In effect, the region from start to end and the region from end to start is the same. Editor
+ * authors, be ready to accept a start that is greater than end.
+ *
+ * @param start the character index where the replacement should start.
+ * @param end the character index where the replacement should end.
+ * @param newCursorPosition the new cursor position around the text. If > 0, this is relative to
+ * the end of the text - 1; if <= 0, this is relative to the start of the text. So a value
+ * of 1 will always advance you to the position after the full text being inserted. Note
+ * that this means you can't position the cursor within the text.
+ * @param text the text to replace. This may include styles.
+ * @param textAttribute The extra information about the text. This value may be null.
+ */
+ default boolean replaceText(
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ @NonNull CharSequence text,
+ int newCursorPosition,
+ @Nullable TextAttribute textAttribute) {
+ Preconditions.checkArgumentNonnegative(start);
+ Preconditions.checkArgumentNonnegative(end);
+
+ beginBatchEdit();
+ finishComposingText();
+ setSelection(start, end);
+ commitText(text, newCursorPosition, textAttribute);
+ endBatchEdit();
+ return true;
+ }
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 424b8ae..8f590f8 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -572,8 +572,8 @@
final Layout layout = mTextView.getLayout();
final int line = layout.getLineForOffset(mTextView.getSelectionStart());
- final int sourceHeight =
- layout.getLineBottomWithoutSpacing(line) - layout.getLineTop(line);
+ final int sourceHeight = layout.getLineBottom(line, /* includeLineSpacing= */ false)
+ - layout.getLineTop(line);
final int height = (int)(sourceHeight * zoom);
final int width = (int)(aspectRatio * Math.max(sourceHeight, mMinLineHeightForMagnifier));
@@ -2340,7 +2340,7 @@
final int offset = mTextView.getSelectionStart();
final int line = layout.getLineForOffset(offset);
final int top = layout.getLineTop(line);
- final int bottom = layout.getLineBottomWithoutSpacing(line);
+ final int bottom = layout.getLineBottom(line, /* includeLineSpacing= */ false);
final boolean clamped = layout.shouldClampCursor(line);
updateCursorPosition(top, bottom, layout.getPrimaryHorizontal(offset, clamped));
@@ -3443,7 +3443,7 @@
@Override
protected int getVerticalLocalPosition(int line) {
final Layout layout = mTextView.getLayout();
- return layout.getLineBottomWithoutSpacing(line);
+ return layout.getLineBottom(line, /* includeLineSpacing= */ false);
}
@Override
@@ -4109,7 +4109,8 @@
@Override
protected int getVerticalLocalPosition(int line) {
final Layout layout = mTextView.getLayout();
- return layout.getLineBottomWithoutSpacing(line) - mContainerMarginTop;
+ return layout.getLineBottom(line, /* includeLineSpacing= */ false)
+ - mContainerMarginTop;
}
@Override
@@ -4706,8 +4707,9 @@
+ viewportToContentVerticalOffset;
final float insertionMarkerBaseline = layout.getLineBaseline(line)
+ viewportToContentVerticalOffset;
- final float insertionMarkerBottom = layout.getLineBottomWithoutSpacing(line)
- + viewportToContentVerticalOffset;
+ final float insertionMarkerBottom =
+ layout.getLineBottom(line, /* includeLineSpacing= */ false)
+ + viewportToContentVerticalOffset;
final boolean isTopVisible = mTextView
.isPositionVisible(insertionMarkerX, insertionMarkerTop);
final boolean isBottomVisible = mTextView
@@ -5137,7 +5139,7 @@
mPositionX = getCursorHorizontalPosition(layout, offset) - mHotspotX
- getHorizontalOffset() + getCursorOffset();
- mPositionY = layout.getLineBottomWithoutSpacing(line);
+ mPositionY = layout.getLineBottom(line, /* includeLineSpacing= */ false);
// Take TextView's padding and scroll into account.
mPositionX += mTextView.viewportToContentHorizontalOffset();
@@ -5233,8 +5235,8 @@
if (mNewMagnifierEnabled) {
Layout layout = mTextView.getLayout();
final int line = layout.getLineForOffset(getCurrentCursorOffset());
- return layout.getLineBottomWithoutSpacing(line) - layout.getLineTop(line)
- >= mMaxLineHeightForMagnifier;
+ return layout.getLineBottom(line, /* includeLineSpacing= */ false)
+ - layout.getLineTop(line) >= mMaxLineHeightForMagnifier;
}
final float magnifierContentHeight = Math.round(
mMagnifierAnimator.mMagnifier.getHeight()
@@ -5389,7 +5391,8 @@
// Vertically snap to middle of current line.
showPosInView.y = ((mTextView.getLayout().getLineTop(lineNumber)
- + mTextView.getLayout().getLineBottomWithoutSpacing(lineNumber)) / 2.0f
+ + mTextView.getLayout()
+ .getLineBottom(lineNumber, /* includeLineSpacing= */ false)) / 2.0f
+ mTextView.getTotalPaddingTop() - mTextView.getScrollY()) * mTextViewScaleY;
return true;
}
@@ -5473,7 +5476,8 @@
updateCursorPosition();
}
final int lineHeight =
- layout.getLineBottomWithoutSpacing(line) - layout.getLineTop(line);
+ layout.getLineBottom(line, /* includeLineSpacing= */ false)
+ - layout.getLineTop(line);
float zoom = mInitialZoom;
if (lineHeight < mMinLineHeightForMagnifier) {
zoom = zoom * mMinLineHeightForMagnifier / lineHeight;
@@ -5823,8 +5827,8 @@
private MotionEvent transformEventForTouchThrough(MotionEvent ev) {
final Layout layout = mTextView.getLayout();
final int line = layout.getLineForOffset(getCurrentCursorOffset());
- final int textHeight =
- layout.getLineBottomWithoutSpacing(line) - layout.getLineTop(line);
+ final int textHeight = layout.getLineBottom(line, /* includeLineSpacing= */ false)
+ - layout.getLineTop(line);
// Transforms the touch events to screen coordinates.
// And also shift up to make the hit point is on the text.
// Note:
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 41d00a2..752d02c 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9330,9 +9330,58 @@
if (range == null) {
return handleGestureFailure(gesture);
}
- getEditableText().delete(range[0], range[1]);
- Selection.setSelection(getEditableText(), range[0]);
- // TODO(b/243983058): Delete extra spaces.
+ int start = range[0];
+ int end = range[1];
+
+ // For word granularity, adjust the start and end offsets to remove extra whitespace around
+ // the deleted text.
+ if (gesture.getGranularity() == HandwritingGesture.GRANULARITY_WORD) {
+ // If the deleted text is at the start of the text, the behavior is the same as the case
+ // where the deleted text follows a new line character.
+ int codePointBeforeStart = start > 0
+ ? Character.codePointBefore(mText, start) : TextUtils.LINE_FEED_CODE_POINT;
+ // If the deleted text is at the end of the text, the behavior is the same as the case
+ // where the deleted text precedes a new line character.
+ int codePointAtEnd = end < mText.length()
+ ? Character.codePointAt(mText, end) : TextUtils.LINE_FEED_CODE_POINT;
+ if (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart)
+ && (TextUtils.isWhitespace(codePointAtEnd)
+ || TextUtils.isPunctuation(codePointAtEnd))) {
+ // Remove whitespace (except new lines) before the deleted text, in these cases:
+ // - There is whitespace following the deleted text
+ // e.g. "one [deleted] three" -> "one | three" -> "one| three"
+ // - There is punctuation following the deleted text
+ // e.g. "one [deleted]!" -> "one |!" -> "one|!"
+ // - There is a new line following the deleted text
+ // e.g. "one [deleted]\n" -> "one |\n" -> "one|\n"
+ // - The deleted text is at the end of the text
+ // e.g. "one [deleted]" -> "one |" -> "one|"
+ // (The pipe | indicates the cursor position.)
+ while (start > 0 && TextUtils.isWhitespaceExceptNewline(codePointBeforeStart)) {
+ start -= Character.charCount(codePointBeforeStart);
+ codePointBeforeStart = Character.codePointBefore(mText, start);
+ }
+ } else if (TextUtils.isWhitespaceExceptNewline(codePointAtEnd)
+ && (TextUtils.isWhitespace(codePointBeforeStart)
+ || TextUtils.isPunctuation(codePointBeforeStart))) {
+ // Remove whitespace (except new lines) after the deleted text, in these cases:
+ // - There is punctuation preceding the deleted text
+ // e.g. "([deleted] two)" -> "(| two)" -> "(|two)"
+ // - There is a new line preceding the deleted text
+ // e.g. "\n[deleted] two" -> "\n| two" -> "\n|two"
+ // - The deleted text is at the start of the text
+ // e.g. "[deleted] two" -> "| two" -> "|two"
+ // (The pipe | indicates the cursor position.)
+ while (end < mText.length()
+ && TextUtils.isWhitespaceExceptNewline(codePointAtEnd)) {
+ end += Character.charCount(codePointAtEnd);
+ codePointAtEnd = Character.codePointAt(mText, end);
+ }
+ }
+ }
+
+ getEditableText().delete(start, end);
+ Selection.setSelection(getEditableText(), start);
return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
}
@@ -9341,7 +9390,7 @@
PointF point = convertFromScreenToContentCoordinates(gesture.getInsertionPoint());
int line = mLayout.getLineForVertical((int) point.y);
if (point.y < mLayout.getLineTop(line)
- || point.y > mLayout.getLineBottomWithoutSpacing(line)) {
+ || point.y > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) {
return handleGestureFailure(gesture);
}
if (point.x < mLayout.getLineLeft(line) || point.x > mLayout.getLineRight(line)) {
@@ -9369,7 +9418,7 @@
// Both points are above the top of the first line.
return handleGestureFailure(gesture);
}
- if (yMin > mLayout.getLineBottomWithoutSpacing(line)) {
+ if (yMin > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) {
if (line == mLayout.getLineCount() - 1 || yMax < mLayout.getLineTop(line + 1)) {
// The points are below the last line, or they are between two lines.
return handleGestureFailure(gesture);
@@ -9423,7 +9472,7 @@
int line = mLayout.getLineForVertical((int) point.y);
if (point.y < mLayout.getLineTop(line)
- || point.y > mLayout.getLineBottomWithoutSpacing(line)) {
+ || point.y > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) {
return handleGestureFailure(gesture);
}
if (point.x < mLayout.getLineLeft(line) || point.x > mLayout.getLineRight(line)) {
diff --git a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
index 17f9b7d..ea5c9a3 100644
--- a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
+++ b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
@@ -137,4 +137,7 @@
int afterLength, int flags, in AndroidFuture future /* T=SurroundingText */);
void setImeConsumesInput(in InputConnectionCommandHeader header, boolean imeConsumesInput);
+
+ void replaceText(in InputConnectionCommandHeader header, int start, int end, CharSequence text,
+ int newCursorPosition,in TextAttribute textAttribute);
}
diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
index 713e913..fcaa1e1 100644
--- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
@@ -1185,6 +1185,30 @@
});
}
+ @Dispatching(cancellable = true)
+ @Override
+ public void replaceText(
+ InputConnectionCommandHeader header,
+ int start,
+ int end,
+ @NonNull CharSequence text,
+ int newCursorPosition,
+ @Nullable TextAttribute textAttribute) {
+ dispatchWithTracing(
+ "replaceText",
+ () -> {
+ if (header.mSessionId != mCurrentSessionId.get()) {
+ return; // cancelled
+ }
+ InputConnection ic = getInputConnection();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "replaceText on inactive InputConnection");
+ return;
+ }
+ ic.replaceText(start, end, text, newCursorPosition, textAttribute);
+ });
+ }
+
private final IRemoteAccessibilityInputConnection mAccessibilityInputConnection =
new IRemoteAccessibilityInputConnection.Stub() {
@Dispatching(cancellable = true)
diff --git a/core/java/com/android/internal/util/function/pooled/PooledLambda.java b/core/java/com/android/internal/util/function/pooled/PooledLambda.java
index f073c1c0..2bfde24 100755
--- a/core/java/com/android/internal/util/function/pooled/PooledLambda.java
+++ b/core/java/com/android/internal/util/function/pooled/PooledLambda.java
@@ -22,36 +22,24 @@
import android.os.Message;
import com.android.internal.util.function.DecConsumer;
-import com.android.internal.util.function.DecFunction;
import com.android.internal.util.function.DodecConsumer;
-import com.android.internal.util.function.DodecFunction;
import com.android.internal.util.function.HeptConsumer;
-import com.android.internal.util.function.HeptFunction;
import com.android.internal.util.function.HexConsumer;
-import com.android.internal.util.function.HexFunction;
import com.android.internal.util.function.NonaConsumer;
-import com.android.internal.util.function.NonaFunction;
import com.android.internal.util.function.OctConsumer;
-import com.android.internal.util.function.OctFunction;
import com.android.internal.util.function.QuadConsumer;
-import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.QuadPredicate;
import com.android.internal.util.function.QuintConsumer;
-import com.android.internal.util.function.QuintFunction;
import com.android.internal.util.function.QuintPredicate;
import com.android.internal.util.function.TriConsumer;
-import com.android.internal.util.function.TriFunction;
import com.android.internal.util.function.TriPredicate;
import com.android.internal.util.function.UndecConsumer;
-import com.android.internal.util.function.UndecFunction;
import com.android.internal.util.function.pooled.PooledLambdaImpl.LambdaType.ReturnType;
import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
-import java.util.function.Predicate;
import java.util.function.Supplier;
/**
@@ -194,40 +182,6 @@
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1) }
- */
- static <A> PooledSupplier<Boolean> obtainSupplier(
- Predicate<? super A> function,
- A arg1) {
- return acquire(PooledLambdaImpl.sPool,
- function, 1, 0, ReturnType.BOOLEAN, arg1, null, null, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1) }
- */
- static <A, R> PooledSupplier<R> obtainSupplier(
- Function<? super A, ? extends R> function,
- A arg1) {
- return acquire(PooledLambdaImpl.sPool,
- function, 1, 0, ReturnType.OBJECT, arg1, null, null, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -279,42 +233,6 @@
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2) }
- */
- static <A, B> PooledSupplier<Boolean> obtainSupplier(
- BiPredicate<? super A, ? super B> function,
- A arg1, B arg2) {
- return acquire(PooledLambdaImpl.sPool,
- function, 2, 0, ReturnType.BOOLEAN, arg1, arg2, null, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2) }
- */
- static <A, B, R> PooledSupplier<R> obtainSupplier(
- BiFunction<? super A, ? super B, ? extends R> function,
- A arg1, B arg2) {
- return acquire(PooledLambdaImpl.sPool,
- function, 2, 0, ReturnType.OBJECT, arg1, arg2, null, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* {@link PooledConsumer} factory
*
* @param function non-capturing lambda(typically an unbounded method reference)
@@ -411,24 +329,6 @@
}
/**
- * {@link PooledFunction} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 placeholder for a missing argument. Use {@link #__} to get one
- * @param arg2 parameter supplied to {@code function} on call
- * @return a {@link PooledFunction}, equivalent to lambda:
- * {@code (arg1) -> function(arg1, arg2) }
- */
- static <A, B, R> PooledFunction<A, R> obtainFunction(
- BiFunction<? super A, ? super B, ? extends R> function,
- ArgumentPlaceholder<A> arg1, B arg2) {
- return acquire(PooledLambdaImpl.sPool,
- function, 2, 1, ReturnType.OBJECT, arg1, arg2, null, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* {@link PooledConsumer} factory
*
* @param function non-capturing lambda(typically an unbounded method reference)
@@ -465,24 +365,6 @@
}
/**
- * {@link PooledFunction} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 placeholder for a missing argument. Use {@link #__} to get one
- * @return a {@link PooledFunction}, equivalent to lambda:
- * {@code (arg2) -> function(arg1, arg2) }
- */
- static <A, B, R> PooledFunction<B, R> obtainFunction(
- BiFunction<? super A, ? super B, ? extends R> function,
- A arg1, ArgumentPlaceholder<B> arg2) {
- return acquire(PooledLambdaImpl.sPool,
- function, 2, 1, ReturnType.OBJECT, arg1, arg2, null, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -536,25 +418,6 @@
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3) }
- */
- static <A, B, C, R> PooledSupplier<R> obtainSupplier(
- TriFunction<? super A, ? super B, ? super C, ? extends R> function,
- A arg1, B arg2, C arg3) {
- return acquire(PooledLambdaImpl.sPool,
- function, 3, 0, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* {@link PooledConsumer} factory
*
* @param function non-capturing lambda(typically an unbounded method reference)
@@ -574,25 +437,6 @@
}
/**
- * {@link PooledFunction} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 placeholder for a missing argument. Use {@link #__} to get one
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @return a {@link PooledFunction}, equivalent to lambda:
- * {@code (arg1) -> function(arg1, arg2, arg3) }
- */
- static <A, B, C, R> PooledFunction<A, R> obtainFunction(
- TriFunction<? super A, ? super B, ? super C, ? extends R> function,
- ArgumentPlaceholder<A> arg1, B arg2, C arg3) {
- return acquire(PooledLambdaImpl.sPool,
- function, 3, 1, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* {@link PooledConsumer} factory
*
* @param function non-capturing lambda(typically an unbounded method reference)
@@ -612,25 +456,6 @@
}
/**
- * {@link PooledFunction} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 placeholder for a missing argument. Use {@link #__} to get one
- * @param arg3 parameter supplied to {@code function} on call
- * @return a {@link PooledFunction}, equivalent to lambda:
- * {@code (arg2) -> function(arg1, arg2, arg3) }
- */
- static <A, B, C, R> PooledFunction<B, R> obtainFunction(
- TriFunction<? super A, ? super B, ? super C, ? extends R> function,
- A arg1, ArgumentPlaceholder<B> arg2, C arg3) {
- return acquire(PooledLambdaImpl.sPool,
- function, 3, 1, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* {@link PooledConsumer} factory
*
* @param function non-capturing lambda(typically an unbounded method reference)
@@ -650,25 +475,6 @@
}
/**
- * {@link PooledFunction} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 placeholder for a missing argument. Use {@link #__} to get one
- * @return a {@link PooledFunction}, equivalent to lambda:
- * {@code (arg3) -> function(arg1, arg2, arg3) }
- */
- static <A, B, C, R> PooledFunction<C, R> obtainFunction(
- TriFunction<? super A, ? super B, ? super C, ? extends R> function,
- A arg1, B arg2, ArgumentPlaceholder<C> arg3) {
- return acquire(PooledLambdaImpl.sPool,
- function, 3, 1, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -724,26 +530,6 @@
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3, arg4) }
- */
- static <A, B, C, D, R> PooledSupplier<R> obtainSupplier(
- QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
- A arg1, B arg2, C arg3, D arg4) {
- return acquire(PooledLambdaImpl.sPool,
- function, 4, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* {@link PooledConsumer} factory
*
* @param function non-capturing lambda(typically an unbounded method reference)
@@ -764,26 +550,6 @@
}
/**
- * {@link PooledFunction} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 placeholder for a missing argument. Use {@link #__} to get one
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @return a {@link PooledFunction}, equivalent to lambda:
- * {@code (arg1) -> function(arg1, arg2, arg3, arg4) }
- */
- static <A, B, C, D, R> PooledFunction<A, R> obtainFunction(
- QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
- ArgumentPlaceholder<A> arg1, B arg2, C arg3, D arg4) {
- return acquire(PooledLambdaImpl.sPool,
- function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* {@link PooledConsumer} factory
*
* @param function non-capturing lambda(typically an unbounded method reference)
@@ -804,26 +570,6 @@
}
/**
- * {@link PooledFunction} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 placeholder for a missing argument. Use {@link #__} to get one
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @return a {@link PooledFunction}, equivalent to lambda:
- * {@code (arg2) -> function(arg1, arg2, arg3, arg4) }
- */
- static <A, B, C, D, R> PooledFunction<B, R> obtainFunction(
- QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
- A arg1, ArgumentPlaceholder<B> arg2, C arg3, D arg4) {
- return acquire(PooledLambdaImpl.sPool,
- function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* {@link PooledConsumer} factory
*
* @param function non-capturing lambda(typically an unbounded method reference)
@@ -844,26 +590,6 @@
}
/**
- * {@link PooledFunction} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 placeholder for a missing argument. Use {@link #__} to get one
- * @param arg4 parameter supplied to {@code function} on call
- * @return a {@link PooledFunction}, equivalent to lambda:
- * {@code (arg3) -> function(arg1, arg2, arg3, arg4) }
- */
- static <A, B, C, D, R> PooledFunction<C, R> obtainFunction(
- QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
- A arg1, B arg2, ArgumentPlaceholder<C> arg3, D arg4) {
- return acquire(PooledLambdaImpl.sPool,
- function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* {@link PooledConsumer} factory
*
* @param function non-capturing lambda(typically an unbounded method reference)
@@ -884,26 +610,6 @@
}
/**
- * {@link PooledFunction} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 placeholder for a missing argument. Use {@link #__} to get one
- * @return a {@link PooledFunction}, equivalent to lambda:
- * {@code (arg4) -> function(arg1, arg2, arg3, arg4) }
- */
- static <A, B, C, D, R> PooledFunction<D, R> obtainFunction(
- QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
- A arg1, B arg2, C arg3, ArgumentPlaceholder<D> arg4) {
- return acquire(PooledLambdaImpl.sPool,
- function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -961,27 +667,6 @@
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @param arg5 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3, arg4, arg5) }
- */
- static <A, B, C, D, E, R> PooledSupplier<R> obtainSupplier(
- QuintFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? extends R>
- function, A arg1, B arg2, C arg3, D arg4, E arg5) {
- return acquire(PooledLambdaImpl.sPool,
- function, 5, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, null, null, null,
- null, null, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -1042,28 +727,6 @@
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @param arg5 parameter supplied to {@code function} on call
- * @param arg6 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6) }
- */
- static <A, B, C, D, E, F, R> PooledSupplier<R> obtainSupplier(
- HexFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
- ? extends R> function, A arg1, B arg2, C arg3, D arg4, E arg5, F arg6) {
- return acquire(PooledLambdaImpl.sPool,
- function, 6, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, null, null,
- null, null, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -1126,30 +789,6 @@
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @param arg5 parameter supplied to {@code function} on call
- * @param arg6 parameter supplied to {@code function} on call
- * @param arg7 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7) }
- */
- static <A, B, C, D, E, F, G, R> PooledSupplier<R> obtainSupplier(
- HeptFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
- ? super G, ? extends R> function,
- A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7) {
- return acquire(PooledLambdaImpl.sPool,
- function, 7, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, null,
- null, null, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -1215,31 +854,6 @@
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @param arg5 parameter supplied to {@code function} on call
- * @param arg6 parameter supplied to {@code function} on call
- * @param arg7 parameter supplied to {@code function} on call
- * @param arg8 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) }
- */
- static <A, B, C, D, E, F, G, H, R> PooledSupplier<R> obtainSupplier(
- OctFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
- ? super G, ? super H, ? extends R> function,
- A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8) {
- return acquire(PooledLambdaImpl.sPool,
- function, 8, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
- null, null, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -1308,32 +922,6 @@
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @param arg5 parameter supplied to {@code function} on call
- * @param arg6 parameter supplied to {@code function} on call
- * @param arg7 parameter supplied to {@code function} on call
- * @param arg8 parameter supplied to {@code function} on call
- * @param arg9 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) }
- */
- static <A, B, C, D, E, F, G, H, I, R> PooledSupplier<R> obtainSupplier(
- NonaFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
- ? super G, ? super H, ? super I, ? extends R> function,
- A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8, I arg9) {
- return acquire(PooledLambdaImpl.sPool,
- function, 9, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
- arg9, null, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -1404,33 +992,6 @@
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @param arg5 parameter supplied to {@code function} on call
- * @param arg6 parameter supplied to {@code function} on call
- * @param arg7 parameter supplied to {@code function} on call
- * @param arg8 parameter supplied to {@code function} on call
- * @param arg9 parameter supplied to {@code function} on call
- * @param arg10 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) }
- */
- static <A, B, C, D, E, F, G, H, I, J, R> PooledSupplier<R> obtainSupplier(
- DecFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
- ? super G, ? super H, ? super I, ? super J, ? extends R> function,
- A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8, I arg9, J arg10) {
- return acquire(PooledLambdaImpl.sPool,
- function, 10, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
- arg9, arg10, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -1504,36 +1065,6 @@
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @param arg5 parameter supplied to {@code function} on call
- * @param arg6 parameter supplied to {@code function} on call
- * @param arg7 parameter supplied to {@code function} on call
- * @param arg8 parameter supplied to {@code function} on call
- * @param arg9 parameter supplied to {@code function} on call
- * @param arg10 parameter supplied to {@code function} on call
- * @param arg11 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10,
- * arg11) }
- */
- static <A, B, C, D, E, F, G, H, I, J, K, R> PooledSupplier<R> obtainSupplier(
- UndecFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
- ? super G, ? super H, ? super I, ? super J, ? super K, ? extends R> function,
- A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8, I arg9, J arg10,
- K arg11) {
- return acquire(PooledLambdaImpl.sPool,
- function, 11, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
- arg9, arg10, arg11, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -1611,38 +1142,6 @@
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @param arg5 parameter supplied to {@code function} on call
- * @param arg6 parameter supplied to {@code function} on call
- * @param arg7 parameter supplied to {@code function} on call
- * @param arg8 parameter supplied to {@code function} on call
- * @param arg9 parameter supplied to {@code function} on call
- * @param arg10 parameter supplied to {@code function} on call
- * @param arg11 parameter supplied to {@code function} on call
- * @param arg12 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10,
- * arg11) }
- */
- static <A, B, C, D, E, F, G, H, I, J, K, L, R> PooledSupplier<R> obtainSupplier(
- DodecFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
- ? super G, ? super H, ? super I, ? super J, ? super K, ? extends L,
- ? extends R> function,
- A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8, I arg9, J arg10,
- K arg11, L arg12) {
- return acquire(PooledLambdaImpl.sPool,
- function, 11, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
- arg9, arg10, arg11, arg12);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 4bbfee2..59e01bf 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -61,7 +61,6 @@
import "frameworks/base/core/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/section.proto";
import "frameworks/base/proto/src/ipconnectivity.proto";
-import "packages/modules/Permission/service/proto/role_service.proto";
package android.os;
@@ -358,10 +357,7 @@
(section).userdebug_and_eng_only = true
];
- optional com.android.role.RoleServiceDumpProto role = 3024 [
- (section).type = SECTION_DUMPSYS,
- (section).args = "role --proto"
- ];
+ reserved 3024;
optional android.service.restricted_image.RestrictedImagesDumpProto restricted_images = 3025 [
(section).type = SECTION_DUMPSYS,
diff --git a/core/tests/coretests/src/android/text/LayoutGetRangeForRectTest.java b/core/tests/coretests/src/android/text/LayoutGetRangeForRectTest.java
index 32fdb5e..787a405 100644
--- a/core/tests/coretests/src/android/text/LayoutGetRangeForRectTest.java
+++ b/core/tests/coretests/src/android/text/LayoutGetRangeForRectTest.java
@@ -90,7 +90,8 @@
mLineCenters = new float[mLayout.getLineCount()];
for (int i = 0; i < mLayout.getLineCount(); ++i) {
- mLineCenters[i] = (mLayout.getLineTop(i) + mLayout.getLineBottomWithoutSpacing(i)) / 2f;
+ mLineCenters[i] = (mLayout.getLineTop(i)
+ + mLayout.getLineBottom(i, /* includeLineSpacing= */ false)) / 2f;
}
mGraphemeClusterSegmentIterator =
diff --git a/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java b/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java
index 2bb5abe..4007c43 100644
--- a/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java
@@ -619,6 +619,74 @@
verifyTextSnapshotContentEquals(mBaseInputConnection.takeSnapshot(), expectedTextSnapshot);
}
+ @Test
+ public void testReplaceText_toEditorWithoutSelectionAndComposing() {
+ // before replace: "|"
+ // after replace: "text1|"
+ assertThat(mBaseInputConnection.replaceText(0, 0, "text1", 1, null)).isTrue();
+ verifyContent("text1", 5, 5, -1, -1);
+
+ // before replace: "text1|"
+ // after replace: "text2|"
+ assertThat(mBaseInputConnection.replaceText(0, 5, "text2", 1, null)).isTrue();
+ verifyContent("text2", 5, 5, -1, -1);
+
+ // before replace: "text1|"
+ // after replace: "|text3"
+ assertThat(mBaseInputConnection.replaceText(0, 5, "text3", -1, null)).isTrue();
+ verifyContent("text3", 0, 0, -1, -1);
+
+ // before replace: "|text3"
+ // after replace: "ttext4|t3"
+ // BUG(b/21476564): this behavior is inconsistent with API description.
+ assertThat(mBaseInputConnection.replaceText(1, 3, "text4", 1, null)).isTrue();
+ verifyContent("ttext4t3", 6, 6, -1, -1);
+
+ // before replace: "ttext4|t3"
+ // after replace: "|text5t3"
+ assertThat(mBaseInputConnection.replaceText(0, 6, "text5", -1, null)).isTrue();
+ verifyContent("text5t3", 0, 0, -1, -1);
+ }
+
+ @Test
+ public void testReplaceText_toEditorWithSelection() {
+ // before replace: "123|456|789"
+ // before replace: "123text|6789"
+ prepareContent("123456789", 3, 6, -1, -1);
+ assertThat(mBaseInputConnection.replaceText(3, 5, "text", 1, null)).isTrue();
+ verifyContent("123text6789", 7, 7, -1, -1);
+
+ // before replace: "|123|"
+ // before replace: "|text23"
+ prepareContent("123", 0, 3, -1, -1);
+ assertThat(mBaseInputConnection.replaceText(0, 1, "text", 0, null)).isTrue();
+ verifyContent("text23", 0, 0, -1, -1);
+ }
+
+ @Test
+ public void testReplaceText_toEditorWithComposing() {
+ // before replace: "123456|789"
+ // ---
+ // before replace: "123456text|"
+ prepareContent("123456789", 6, 6, 3, 6);
+ assertThat(mBaseInputConnection.replaceText(6, 9, "text", 1, null)).isTrue();
+ verifyContent("123456text", 10, 10, -1, -1);
+
+ // before replace: "123456789|"
+ // ---
+ // before replace: "text|123456789"
+ prepareContent("123456789", 9, 9, 3, 6);
+ assertThat(mBaseInputConnection.replaceText(0, 0, "text", 1, null)).isTrue();
+ verifyContent("text123456789", 4, 4, -1, -1);
+
+ // before replace: "|123456789|"
+ // ---
+ // before replace: "12text|9"
+ prepareContent("123456789", 0, 9, 3, 6);
+ assertThat(mBaseInputConnection.replaceText(2, 8, "text", 1, null)).isTrue();
+ verifyContent("12text9", 6, 6, -1, -1);
+ }
+
private void prepareContent(
CharSequence text,
int selectionStart,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 33074de..f4dda4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -57,6 +57,8 @@
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeController;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
@@ -716,15 +718,36 @@
// Desktop mode (optional feature)
//
+ @WMSingleton
+ @Provides
+ static Optional<DesktopMode> provideDesktopMode(
+ Optional<DesktopModeController> desktopModeController) {
+ return desktopModeController.map(DesktopModeController::asDesktopMode);
+ }
+
+ @BindsOptionalOf
+ @DynamicOverride
+ abstract DesktopModeController optionalDesktopModeController();
+
+ @WMSingleton
+ @Provides
+ static Optional<DesktopModeController> providesDesktopModeController(
+ @DynamicOverride Optional<DesktopModeController> desktopModeController) {
+ if (DesktopModeStatus.IS_SUPPORTED) {
+ return desktopModeController;
+ }
+ return Optional.empty();
+ }
+
@BindsOptionalOf
@DynamicOverride
abstract DesktopModeTaskRepository optionalDesktopModeTaskRepository();
@WMSingleton
@Provides
- static Optional<DesktopModeTaskRepository> providesDesktopModeTaskRepository(
+ static Optional<DesktopModeTaskRepository> providesDesktopTaskRepository(
@DynamicOverride Optional<DesktopModeTaskRepository> desktopModeTaskRepository) {
- if (DesktopMode.IS_SUPPORTED) {
+ if (DesktopModeStatus.IS_SUPPORTED) {
return desktopModeTaskRepository;
}
return Optional.empty();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 35e88e9..e784261 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -48,7 +48,6 @@
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.draganddrop.DragAndDropController;
@@ -595,19 +594,18 @@
@WMSingleton
@Provides
- static Optional<DesktopModeController> provideDesktopModeController(
- Context context, ShellInit shellInit,
+ @DynamicOverride
+ static DesktopModeController provideDesktopModeController(Context context, ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ Transitions transitions,
+ @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
@ShellMainThread Handler mainHandler,
- Transitions transitions
+ @ShellMainThread ShellExecutor mainExecutor
) {
- if (DesktopMode.IS_SUPPORTED) {
- return Optional.of(new DesktopModeController(context, shellInit, shellTaskOrganizer,
- rootTaskDisplayAreaOrganizer, mainHandler, transitions));
- } else {
- return Optional.empty();
- }
+ return new DesktopModeController(context, shellInit, shellTaskOrganizer,
+ rootTaskDisplayAreaOrganizer, transitions, desktopModeTaskRepository, mainHandler,
+ mainExecutor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index 8993d54..ff3be38 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -16,43 +16,16 @@
package com.android.wm.shell.desktopmode;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
-
-import android.content.Context;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ExternalThread;
/**
- * Constants for desktop mode feature
+ * Interface to interact with desktop mode feature in shell.
*/
-public class DesktopMode {
+@ExternalThread
+public interface DesktopMode {
- /**
- * Flag to indicate whether desktop mode is available on the device
- */
- public static final boolean IS_SUPPORTED = SystemProperties.getBoolean(
- "persist.wm.debug.desktop_mode", false);
-
- /**
- * Check if desktop mode is active
- *
- * @return {@code true} if active
- */
- public static boolean isActive(Context context) {
- if (!IS_SUPPORTED) {
- return false;
- }
- try {
- int result = Settings.System.getIntForUser(context.getContentResolver(),
- Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result);
- return result != 0;
- } catch (Exception e) {
- ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
- return false;
- }
+ /** Returns a binder that can be passed to an external process to manipulate DesktopMode. */
+ default IDesktopMode createExternalInterface() {
+ return null;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 6e44d58..9474cfe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -20,8 +20,10 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
import android.content.Context;
import android.database.ContentObserver;
@@ -29,51 +31,83 @@
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.ArraySet;
import android.window.DisplayAreaInfo;
import android.window.WindowContainerTransaction;
+import androidx.annotation.BinderThread;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import java.util.ArrayList;
+import java.util.Comparator;
+
/**
* Handles windowing changes when desktop mode system setting changes
*/
-public class DesktopModeController {
+public class DesktopModeController implements RemoteCallable<DesktopModeController> {
private final Context mContext;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
- private final SettingsObserver mSettingsObserver;
private final Transitions mTransitions;
+ private final DesktopModeTaskRepository mDesktopModeTaskRepository;
+ private final ShellExecutor mMainExecutor;
+ private final DesktopMode mDesktopModeImpl = new DesktopModeImpl();
+ private final SettingsObserver mSettingsObserver;
public DesktopModeController(Context context, ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ Transitions transitions,
+ DesktopModeTaskRepository desktopModeTaskRepository,
@ShellMainThread Handler mainHandler,
- Transitions transitions) {
+ @ShellMainThread ShellExecutor mainExecutor) {
mContext = context;
mShellTaskOrganizer = shellTaskOrganizer;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
- mSettingsObserver = new SettingsObserver(mContext, mainHandler);
mTransitions = transitions;
+ mDesktopModeTaskRepository = desktopModeTaskRepository;
+ mMainExecutor = mainExecutor;
+ mSettingsObserver = new SettingsObserver(mContext, mainHandler);
shellInit.addInitCallback(this::onInit, this);
}
private void onInit() {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
mSettingsObserver.observe();
- if (DesktopMode.isActive(mContext)) {
+ if (DesktopModeStatus.isActive(mContext)) {
updateDesktopModeActive(true);
}
}
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
+ /**
+ * Get connection interface between sysui and shell
+ */
+ public DesktopMode asDesktopMode() {
+ return mDesktopModeImpl;
+ }
+
@VisibleForTesting
void updateDesktopModeActive(boolean active) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeActive: active=%s", active);
@@ -121,6 +155,28 @@
}
/**
+ * Show apps on desktop
+ */
+ public void showDesktopApps() {
+ ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
+ ArrayList<RunningTaskInfo> taskInfos = new ArrayList<>();
+ for (Integer taskId : activeTasks) {
+ RunningTaskInfo taskInfo = mShellTaskOrganizer.getRunningTaskInfo(taskId);
+ if (taskInfo != null) {
+ taskInfos.add(taskInfo);
+ }
+ }
+ // Order by lastActiveTime, descending
+ taskInfos.sort(Comparator.comparingLong(task -> -task.lastActiveTime));
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ for (RunningTaskInfo task : taskInfos) {
+ wct.reorder(task.token, true);
+ }
+ mShellTaskOrganizer.applyTransaction(wct);
+ }
+
+ /**
* A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
*/
private final class SettingsObserver extends ContentObserver {
@@ -150,8 +206,51 @@
}
private void desktopModeSettingChanged() {
- boolean enabled = DesktopMode.isActive(mContext);
+ boolean enabled = DesktopModeStatus.isActive(mContext);
updateDesktopModeActive(enabled);
}
}
+
+ /**
+ * The interface for calls from outside the shell, within the host process.
+ */
+ @ExternalThread
+ private final class DesktopModeImpl implements DesktopMode {
+
+ private IDesktopModeImpl mIDesktopMode;
+
+ @Override
+ public IDesktopMode createExternalInterface() {
+ if (mIDesktopMode != null) {
+ mIDesktopMode.invalidate();
+ }
+ mIDesktopMode = new IDesktopModeImpl(DesktopModeController.this);
+ return mIDesktopMode;
+ }
+ }
+
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class IDesktopModeImpl extends IDesktopMode.Stub {
+
+ private DesktopModeController mController;
+
+ IDesktopModeImpl(DesktopModeController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ void invalidate() {
+ mController = null;
+ }
+
+ public void showDesktopApps() {
+ executeRemoteCallWithTaskPermission(mController, "showDesktopApps",
+ DesktopModeController::showDesktopApps);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
new file mode 100644
index 0000000..195ff50
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.desktopmode;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+
+import android.content.Context;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.internal.protolog.common.ProtoLog;
+
+/**
+ * Constants for desktop mode feature
+ */
+public class DesktopModeStatus {
+
+ /**
+ * Flag to indicate whether desktop mode is available on the device
+ */
+ public static final boolean IS_SUPPORTED = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_mode", false);
+
+ /**
+ * Check if desktop mode is active
+ *
+ * @return {@code true} if active
+ */
+ public static boolean isActive(Context context) {
+ if (!IS_SUPPORTED) {
+ return false;
+ }
+ try {
+ int result = Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result);
+ return result != 0;
+ } catch (Exception e) {
+ ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
+ return false;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
new file mode 100644
index 0000000..5042bd6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.desktopmode;
+
+/**
+ * Interface that is exposed to remote callers to manipulate desktop mode features.
+ */
+interface IDesktopMode {
+
+ /** Show apps on the desktop */
+ void showDesktopApps();
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index f58719b..e2d5a49 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -28,7 +28,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellInit;
@@ -90,7 +90,7 @@
t.apply();
}
- if (DesktopMode.IS_SUPPORTED && taskInfo.isVisible) {
+ if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isVisible) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"Adding active freeform task: #%d", taskInfo.taskId);
mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
@@ -123,7 +123,7 @@
taskInfo.taskId);
mTasks.remove(taskInfo.taskId);
- if (DesktopMode.IS_SUPPORTED) {
+ if (DesktopModeStatus.IS_SUPPORTED) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"Removing active freeform task: #%d", taskInfo.taskId);
mDesktopModeTaskRepository.ifPresent(it -> it.removeActiveTask(taskInfo.taskId));
@@ -150,7 +150,7 @@
mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo, state.mWindowDecoration);
}
- if (DesktopMode.IS_SUPPORTED) {
+ if (DesktopModeStatus.IS_SUPPORTED) {
if (taskInfo.isVisible) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"Adding active freeform task: #%d", taskInfo.taskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index f879994..6409e70 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -44,7 +44,7 @@
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -287,7 +287,7 @@
rawMapping.put(taskInfo.taskId, taskInfo);
}
- boolean desktopModeActive = DesktopMode.isActive(mContext);
+ boolean desktopModeActive = DesktopModeStatus.isActive(mContext);
ArrayList<ActivityManager.RecentTaskInfo> freeformTasks = new ArrayList<>();
// Pull out the pairs as we iterate back in the list
@@ -320,7 +320,6 @@
// Add a special entry for freeform tasks
if (!freeformTasks.isEmpty()) {
- // First task is added separately
recentTasks.add(0, GroupedRecentTaskInfo.forFreeformTasks(
freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0])));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index e8a2cb160..9c7131a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -36,7 +36,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.transition.Transitions;
@@ -239,7 +239,7 @@
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
- return DesktopMode.IS_SUPPORTED
+ return DesktopModeStatus.IS_SUPPORTED
&& mDisplayController.getDisplayContext(taskInfo.displayId)
.getResources().getConfiguration().smallestScreenWidthDp >= 600;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 5040bc3..733f6b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -34,7 +34,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
@@ -164,7 +164,7 @@
View caption = mResult.mRootView.findViewById(R.id.caption);
caption.setOnTouchListener(mOnCaptionTouchListener);
View maximize = caption.findViewById(R.id.maximize_window);
- if (DesktopMode.IS_SUPPORTED) {
+ if (DesktopModeStatus.IS_SUPPORTED) {
// Hide maximize button when desktop mode is available
maximize.setVisibility(View.GONE);
} else {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
index c0720cf..3672ae3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
@@ -44,6 +44,7 @@
private ActivityManager.TaskDescription.Builder mTaskDescriptionBuilder = null;
private final Point mPositionInParent = new Point();
private boolean mIsVisible = false;
+ private long mLastActiveTime;
public static WindowContainerToken createMockWCToken() {
final IWindowContainerToken itoken = mock(IWindowContainerToken.class);
@@ -52,6 +53,11 @@
return new WindowContainerToken(itoken);
}
+ public TestRunningTaskInfoBuilder setToken(WindowContainerToken token) {
+ mToken = token;
+ return this;
+ }
+
public TestRunningTaskInfoBuilder setBounds(Rect bounds) {
mBounds.set(bounds);
return this;
@@ -95,6 +101,11 @@
return this;
}
+ public TestRunningTaskInfoBuilder setLastActiveTime(long lastActiveTime) {
+ mLastActiveTime = lastActiveTime;
+ return this;
+ }
+
public ActivityManager.RunningTaskInfo build() {
final ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
info.taskId = sNextTaskId++;
@@ -110,6 +121,7 @@
mTaskDescriptionBuilder != null ? mTaskDescriptionBuilder.build() : null;
info.positionInParent = mPositionInParent;
info.isVisible = mIsVisible;
+ info.lastActiveTime = mLastActiveTime;
return info;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index c628f399..dd23d97 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -19,16 +19,22 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.WindowConfiguration;
+import android.app.ActivityManager;
import android.os.Handler;
import android.os.IBinder;
import android.testing.AndroidTestingRunner;
@@ -39,13 +45,17 @@
import androidx.test.filters.SmallTest;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -67,18 +77,38 @@
private Handler mMockHandler;
@Mock
private Transitions mMockTransitions;
+ private TestShellExecutor mExecutor;
private DesktopModeController mController;
+ private DesktopModeTaskRepository mDesktopModeTaskRepository;
private ShellInit mShellInit;
+ private StaticMockitoSession mMockitoSession;
@Before
public void setUp() {
+ mMockitoSession = mockitoSession().mockStatic(DesktopModeStatus.class).startMocking();
+ when(DesktopModeStatus.isActive(any())).thenReturn(true);
+
mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
+ mExecutor = new TestShellExecutor();
+
+ mDesktopModeTaskRepository = new DesktopModeTaskRepository();
mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer,
- mRootTaskDisplayAreaOrganizer, mMockHandler, mMockTransitions);
+ mRootTaskDisplayAreaOrganizer, mMockTransitions,
+ mDesktopModeTaskRepository, mMockHandler, mExecutor);
+
+ when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(anyInt())).thenReturn(
+ new WindowContainerTransaction());
mShellInit.init();
+ clearInvocations(mShellTaskOrganizer);
+ clearInvocations(mRootTaskDisplayAreaOrganizer);
+ }
+
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
}
@Test
@@ -159,17 +189,15 @@
assertThat(wct.getChanges()).hasSize(3);
// Verify executed WCT has a change for setting task windowing mode to undefined
- Change taskWmModeChange = wct.getChanges().get(taskWmMockToken.binder());
- assertThat(taskWmModeChange).isNotNull();
- assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+ Change taskWmMode = wct.getChanges().get(taskWmMockToken.binder());
+ assertThat(taskWmMode).isNotNull();
+ assertThat(taskWmMode.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
// Verify executed WCT has a change for clearing task bounds
- Change taskBoundsChange = wct.getChanges().get(taskBoundsMockToken.binder());
- assertThat(taskBoundsChange).isNotNull();
- assertThat(taskBoundsChange.getWindowSetMask()
- & WindowConfiguration.WINDOW_CONFIG_BOUNDS).isNotEqualTo(0);
- assertThat(taskBoundsChange.getConfiguration().windowConfiguration.getBounds().isEmpty())
- .isTrue();
+ Change bounds = wct.getChanges().get(taskBoundsMockToken.binder());
+ assertThat(bounds).isNotNull();
+ assertThat(bounds.getWindowSetMask() & WINDOW_CONFIG_BOUNDS).isNotEqualTo(0);
+ assertThat(bounds.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
// Verify executed WCT has a change for setting display windowing mode to fullscreen
Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
@@ -177,6 +205,41 @@
assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
}
+ @Test
+ public void testShowDesktopApps() {
+ // Set up two active tasks on desktop
+ mDesktopModeTaskRepository.addActiveTask(1);
+ mDesktopModeTaskRepository.addActiveTask(2);
+ MockToken token1 = new MockToken();
+ MockToken token2 = new MockToken();
+ ActivityManager.RunningTaskInfo taskInfo1 = new TestRunningTaskInfoBuilder().setToken(
+ token1.token()).setLastActiveTime(100).build();
+ ActivityManager.RunningTaskInfo taskInfo2 = new TestRunningTaskInfoBuilder().setToken(
+ token2.token()).setLastActiveTime(200).build();
+ when(mShellTaskOrganizer.getRunningTaskInfo(1)).thenReturn(taskInfo1);
+ when(mShellTaskOrganizer.getRunningTaskInfo(2)).thenReturn(taskInfo2);
+
+ // Run show desktop apps logic
+ mController.showDesktopApps();
+ ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass(
+ WindowContainerTransaction.class);
+ verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
+ WindowContainerTransaction wct = wctCaptor.getValue();
+
+ // Check wct has reorder calls
+ assertThat(wct.getHierarchyOps()).hasSize(2);
+
+ // Task 2 has activity later, must be first
+ WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op1.getContainer()).isEqualTo(token2.binder());
+
+ // Task 1 should be second
+ WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(0);
+ assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op2.getContainer()).isEqualTo(token2.binder());
+ }
+
private static class MockToken {
private final WindowContainerToken mToken;
private final IBinder mBinder;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index cadfeb0..70fee2b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -54,7 +54,7 @@
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
@@ -190,8 +190,8 @@
@Test
public void testGetRecentTasks_groupActiveFreeformTasks() {
StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
- DesktopMode.class).startMocking();
- when(DesktopMode.isActive(any())).thenReturn(true);
+ DesktopModeStatus.class).startMocking();
+ when(DesktopModeStatus.isActive(any())).thenReturn(true);
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DisposableBroadcastReceiverAsUser.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
similarity index 96%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DisposableBroadcastReceiverAsUser.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
index 1589b04..a7de4ce 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DisposableBroadcastReceiverAsUser.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.framework.compose
+package com.android.settingslib.spaprivileged.framework.compose
import android.content.BroadcastReceiver
import android.content.Context
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 4222744..2111df5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -237,6 +237,13 @@
public ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
new ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData();
+ /**
+ * Indicates that this task for the desktop tile in recents.
+ *
+ * Used when desktop mode feature is enabled.
+ */
+ public boolean desktopTile;
+
public Task() {
// Do nothing
}
@@ -267,6 +274,7 @@
this(other.key, other.colorPrimary, other.colorBackground, other.isDockable,
other.isLocked, other.taskDescription, other.topActivity);
lastSnapshotData.set(other.lastSnapshotData);
+ desktopTile = other.desktopTile;
}
/**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 6d12485..85278dd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -62,6 +62,8 @@
// See IRecentTasks.aidl
public static final String KEY_EXTRA_RECENT_TASKS = "recent_tasks";
public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
+ // See IDesktopMode.aidl
+ public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
public static final String NAV_BAR_MODE_3BUTTON_OVERLAY =
WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index b78fa9a..71470e8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -57,11 +57,10 @@
val faceLockedOut: Boolean,
val fpLockedOut: Boolean,
val goingToSleep: Boolean,
- val keyguardAwakeExcludingBouncerShowing: Boolean,
+ val keyguardAwake: Boolean,
val keyguardGoingAway: Boolean,
val listeningForFaceAssistant: Boolean,
val occludingAppRequestingFaceAuth: Boolean,
- val onlyFaceEnrolled: Boolean,
val primaryUser: Boolean,
val scanningAllowedByStrongAuth: Boolean,
val secureCameraLaunched: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 4b6177a..f259a54 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2579,11 +2579,8 @@
}
final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED;
- // mKeyguardIsVisible is true even when the bouncer is shown, we don't want to run face auth
- // on bouncer if both fp and fingerprint are enrolled.
- final boolean awakeKeyguardExcludingBouncerShowing = mKeyguardIsVisible
- && mDeviceInteractive && !mGoingToSleep
- && !statusBarShadeLocked && !mBouncerFullyShown;
+ final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep
+ && !statusBarShadeLocked;
final int user = getCurrentUser();
final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
final boolean isLockDown =
@@ -2623,16 +2620,15 @@
final boolean faceDisabledForUser = isFaceDisabled(user);
final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user);
final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant();
- final boolean onlyFaceEnrolled = isOnlyFaceEnrolled();
final boolean fpOrFaceIsLockedOut = isFaceLockedOut() || fpLockedout;
// Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
// instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
final boolean shouldListen =
- ((mBouncerFullyShown && !mGoingToSleep && onlyFaceEnrolled)
+ (mBouncerFullyShown && !mGoingToSleep
|| mAuthInterruptActive
|| mOccludingAppRequestingFace
- || awakeKeyguardExcludingBouncerShowing
+ || awakeKeyguard
|| shouldListenForFaceAssistant
|| mAuthController.isUdfpsFingerDown()
|| mUdfpsBouncerShowing)
@@ -2658,11 +2654,10 @@
isFaceLockedOut(),
fpLockedout,
mGoingToSleep,
- awakeKeyguardExcludingBouncerShowing,
+ awakeKeyguard,
mKeyguardGoingAway,
shouldListenForFaceAssistant,
mOccludingAppRequestingFace,
- onlyFaceEnrolled,
mIsPrimaryUser,
strongAuthAllowsScanning,
mSecureCameraLaunched,
@@ -2672,11 +2667,6 @@
return shouldListen;
}
- private boolean isOnlyFaceEnrolled() {
- return isFaceEnrolled()
- && !getCachedIsUnlockWithFingerprintPossible(sCurrentUser);
- }
-
private void maybeLogListenerModelData(KeyguardListenModel model) {
mLogger.logKeyguardListenerModel(model);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 50c38e5..a21f45f 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -97,7 +97,8 @@
.setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
.setRecentTasks(mWMComponent.getRecentTasks())
.setBackAnimation(mWMComponent.getBackAnimation())
- .setFloatingTasks(mWMComponent.getFloatingTasks());
+ .setFloatingTasks(mWMComponent.getFloatingTasks())
+ .setDesktopMode(mWMComponent.getDesktopMode());
// Only initialize when not starting from tests since this currently initializes some
// components that shouldn't be run in the test environment
@@ -117,7 +118,8 @@
.setStartingSurface(Optional.ofNullable(null))
.setRecentTasks(Optional.ofNullable(null))
.setBackAnimation(Optional.ofNullable(null))
- .setFloatingTasks(Optional.ofNullable(null));
+ .setFloatingTasks(Optional.ofNullable(null))
+ .setDesktopMode(Optional.ofNullable(null));
}
mSysUIComponent = builder.build();
if (initializeComponents) {
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 b8a0013..1f7021e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -18,6 +18,7 @@
import android.annotation.AnyThread
import android.annotation.MainThread
+import android.app.Activity
import android.app.AlertDialog
import android.app.Dialog
import android.app.PendingIntent
@@ -119,8 +120,16 @@
}
override fun closeDialogs() {
- dialog?.dismiss()
- dialog = null
+ val isActivityFinishing =
+ (activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed }
+ if (isActivityFinishing == true) {
+ dialog = null
+ return
+ }
+ if (dialog?.isShowing == true) {
+ dialog?.dismiss()
+ dialog = null
+ }
}
override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 7e30431..0d06c51 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -40,6 +40,7 @@
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.floating.FloatingTasks;
import com.android.wm.shell.onehanded.OneHanded;
@@ -113,6 +114,9 @@
@BindsInstance
Builder setFloatingTasks(Optional<FloatingTasks> f);
+ @BindsInstance
+ Builder setDesktopMode(Optional<DesktopMode> d);
+
SysUIComponent build();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index dd11549..096f969 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -30,6 +30,7 @@
import com.android.wm.shell.dagger.TvWMShellModule;
import com.android.wm.shell.dagger.WMShellModule;
import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.floating.FloatingTasks;
import com.android.wm.shell.onehanded.OneHanded;
@@ -112,4 +113,10 @@
@WMSingleton
Optional<FloatingTasks> getFloatingTasks();
+
+ /**
+ * Optional {@link DesktopMode} component for interacting with desktop mode.
+ */
+ @WMSingleton
+ Optional<DesktopMode> getDesktopMode();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
index 21a51d1..c07d402 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
@@ -18,13 +18,21 @@
import static com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent.DreamMediaEntryModule.DREAM_MEDIA_ENTRY_VIEW;
import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_MEDIA_ENTRY_LAYOUT_PARAMS;
+import static com.android.systemui.flags.Flags.DREAM_MEDIA_TAP_TO_OPEN;
+import android.app.PendingIntent;
import android.util.Log;
import android.view.View;
+import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.media.MediaCarouselController;
import com.android.systemui.media.dream.MediaDreamComplication;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
@@ -87,6 +95,15 @@
private final DreamOverlayStateController mDreamOverlayStateController;
private final MediaDreamComplication mMediaComplication;
+ private final MediaCarouselController mMediaCarouselController;
+
+ private final ActivityStarter mActivityStarter;
+ private final ActivityIntentHelper mActivityIntentHelper;
+ private final KeyguardStateController mKeyguardStateController;
+ private final NotificationLockscreenUserManager mLockscreenUserManager;
+
+ private final FeatureFlags mFeatureFlags;
+ private boolean mIsTapToOpenEnabled;
private boolean mMediaComplicationAdded;
@@ -94,15 +111,28 @@
DreamMediaEntryViewController(
@Named(DREAM_MEDIA_ENTRY_VIEW) View view,
DreamOverlayStateController dreamOverlayStateController,
- MediaDreamComplication mediaComplication) {
+ MediaDreamComplication mediaComplication,
+ MediaCarouselController mediaCarouselController,
+ ActivityStarter activityStarter,
+ ActivityIntentHelper activityIntentHelper,
+ KeyguardStateController keyguardStateController,
+ NotificationLockscreenUserManager lockscreenUserManager,
+ FeatureFlags featureFlags) {
super(view);
mDreamOverlayStateController = dreamOverlayStateController;
mMediaComplication = mediaComplication;
+ mMediaCarouselController = mediaCarouselController;
+ mActivityStarter = activityStarter;
+ mActivityIntentHelper = activityIntentHelper;
+ mKeyguardStateController = keyguardStateController;
+ mLockscreenUserManager = lockscreenUserManager;
+ mFeatureFlags = featureFlags;
mView.setOnClickListener(this::onClickMediaEntry);
}
@Override
protected void onViewAttached() {
+ mIsTapToOpenEnabled = mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN);
}
@Override
@@ -113,6 +143,31 @@
private void onClickMediaEntry(View v) {
if (DEBUG) Log.d(TAG, "media entry complication tapped");
+ if (mIsTapToOpenEnabled) {
+ final PendingIntent clickIntent =
+ mMediaCarouselController.getCurrentVisibleMediaContentIntent();
+
+ if (clickIntent == null) {
+ return;
+ }
+
+ // See StatusBarNotificationActivityStarter#onNotificationClicked
+ final boolean showOverLockscreen = mKeyguardStateController.isShowing()
+ && mActivityIntentHelper.wouldShowOverLockscreen(clickIntent.getIntent(),
+ mLockscreenUserManager.getCurrentUserId());
+
+ if (showOverLockscreen) {
+ mActivityStarter.startActivity(clickIntent.getIntent(),
+ /* dismissShade */ true,
+ /* animationController */ null,
+ /* showOverLockscreenWhenLocked */ true);
+ } else {
+ mActivityStarter.postStartActivityDismissingKeyguard(clickIntent, null);
+ }
+
+ return;
+ }
+
if (!mMediaComplicationAdded) {
addMediaComplication();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 223d79a..54c1b28 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -200,7 +200,8 @@
public static final UnreleasedFlag MEDIA_SESSION_ACTIONS = new UnreleasedFlag(901);
public static final ReleasedFlag MEDIA_NEARBY_DEVICES = new ReleasedFlag(903);
public static final ReleasedFlag MEDIA_MUTE_AWAIT = new ReleasedFlag(904);
- public static final UnreleasedFlag MEDIA_DREAM_COMPLICATION = new UnreleasedFlag(905);
+ public static final UnreleasedFlag DREAM_MEDIA_COMPLICATION = new UnreleasedFlag(905);
+ public static final UnreleasedFlag DREAM_MEDIA_TAP_TO_OPEN = new UnreleasedFlag(906);
// 1000 - dock
public static final ReleasedFlag SIMULATE_DOCK_THROUGH_CHARGING =
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index b36f33b..f1e54e0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -1,5 +1,6 @@
package com.android.systemui.media
+import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
@@ -945,6 +946,11 @@
mediaManager.onSwipeToDismiss()
}
+ fun getCurrentVisibleMediaContentIntent(): PendingIntent? {
+ return MediaPlayerData.playerKeys()
+ .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)?.data?.clickIntent
+ }
+
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.apply {
println("keysNeedRemoval: $keysNeedRemoval")
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index b516689..a9e1a4d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -516,7 +516,7 @@
abstract int getStopButtonVisibility();
public CharSequence getStopButtonText() {
- return mContext.getText(R.string.media_output_dialog_button_stop_casting);
+ return mContext.getText(R.string.keyboard_key_media_stop);
}
public void onStopButtonClick() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index cb6f5a7..fbd0079 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -108,7 +108,7 @@
@Override
public CharSequence getStopButtonText() {
- int resId = R.string.media_output_dialog_button_stop_casting;
+ int resId = R.string.keyboard_key_media_stop;
if (isBroadcastSupported() && mMediaOutputController.isPlaying()
&& !mMediaOutputController.isBluetoothLeBroadcastEnabled()) {
resId = R.string.media_output_broadcast;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
index dc1488e..53b4d43 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
@@ -16,7 +16,7 @@
package com.android.systemui.media.dream;
-import static com.android.systemui.flags.Flags.MEDIA_DREAM_COMPLICATION;
+import static com.android.systemui.flags.Flags.DREAM_MEDIA_COMPLICATION;
import android.content.Context;
import android.util.Log;
@@ -77,7 +77,7 @@
public void onMediaDataLoaded(@NonNull String key, @Nullable String oldKey,
@NonNull MediaData data, boolean immediately, int receivedSmartspaceCardLatency,
boolean isSsReactivated) {
- if (!mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)) {
+ if (!mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 95edb35..7e2a5c5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -27,6 +27,7 @@
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_BACK_ANIMATION;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_DESKTOP_MODE;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_FLOATING_TASKS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
@@ -110,6 +111,7 @@
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.wm.shell.back.BackAnimation;
+import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.floating.FloatingTasks;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
@@ -169,6 +171,7 @@
private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
private final Optional<RecentTasks> mRecentTasks;
private final Optional<BackAnimation> mBackAnimation;
+ private final Optional<DesktopMode> mDesktopModeOptional;
private final UiEventLogger mUiEventLogger;
private Region mActiveNavBarRegion;
@@ -488,6 +491,9 @@
mBackAnimation.ifPresent((backAnimation) -> params.putBinder(
KEY_EXTRA_SHELL_BACK_ANIMATION,
backAnimation.createExternalInterface().asBinder()));
+ mDesktopModeOptional.ifPresent((desktopMode -> params.putBinder(
+ KEY_EXTRA_SHELL_DESKTOP_MODE,
+ desktopMode.createExternalInterface().asBinder())));
try {
Log.d(TAG_OPS, "OverviewProxyService connected, initializing overview proxy");
@@ -573,6 +579,7 @@
Optional<RecentTasks> recentTasks,
Optional<BackAnimation> backAnimation,
Optional<StartingSurface> startingSurface,
+ Optional<DesktopMode> desktopModeOptional,
BroadcastDispatcher broadcastDispatcher,
ShellTransitions shellTransitions,
ScreenLifecycle screenLifecycle,
@@ -607,6 +614,7 @@
mShellTransitions = shellTransitions;
mRecentTasks = recentTasks;
mBackAnimation = backAnimation;
+ mDesktopModeOptional = desktopModeOptional;
mUiEventLogger = uiEventLogger;
dumpManager.registerDumpable(getClass().getSimpleName(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 41c0367..d67f94f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -168,6 +168,17 @@
return new ShelfState();
}
+ @Override
+ public String toString() {
+ return "NotificationShelf("
+ + "hideBackground=" + mHideBackground + " notGoneIndex=" + mNotGoneIndex
+ + " hasItemsInStableShelf=" + mHasItemsInStableShelf
+ + " statusBarState=" + mStatusBarState + " interactive=" + mInteractive
+ + " animationsEnabled=" + mAnimationsEnabled
+ + " showNotificationShelf=" + mShowNotificationShelf
+ + " indexOfFirstViewInShelf=" + mIndexOfFirstViewInShelf + ')';
+ }
+
/** Update the state of the shelf. */
public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
AmbientState ambientState) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 039a362..827d0d0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -36,7 +36,6 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
-import android.os.Parcelable;
import android.os.Trace;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
@@ -551,9 +550,12 @@
}
}
+ @Override
public String toString() {
- return "StatusBarIconView(slot=" + mSlot + " icon=" + mIcon
- + " notification=" + mNotification + ")";
+ return "StatusBarIconView("
+ + "slot='" + mSlot + " alpha=" + getAlpha() + " icon=" + mIcon
+ + " iconColor=#" + Integer.toHexString(mIconColor)
+ + " notification=" + mNotification + ')';
}
public StatusBarNotification getNotification() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 1eafaf0..f7ce43b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -3348,7 +3348,8 @@
// lock screen where users can use the UDFPS affordance to enter the device
mStatusBarKeyguardViewManager.reset(true);
} else if (mState == StatusBarState.KEYGUARD
- && !mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()) {
+ && !mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()
+ && isKeyguardSecure()) {
mStatusBarKeyguardViewManager.showGenericBouncer(true /* scrimmed */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 7b8c5fc..5a70d89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -278,6 +278,15 @@
}
}
+ @Override
+ public String toString() {
+ return "NotificationIconContainer("
+ + "dozing=" + mDozing + " onLockScreen=" + mOnLockScreen
+ + " inNotificationIconShelf=" + mInNotificationIconShelf
+ + " speedBumpIndex=" + mSpeedBumpIndex
+ + " themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary) + ')';
+ }
+
@VisibleForTesting
public void setIconSize(int size) {
mIconSize = size;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index 485a7e5..aca60c0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -86,13 +86,12 @@
becauseCannotSkipBouncer = false,
biometricSettingEnabledForUser = false,
bouncerFullyShown = false,
- onlyFaceEnrolled = false,
faceAuthenticated = false,
faceDisabled = false,
faceLockedOut = false,
fpLockedOut = false,
goingToSleep = false,
- keyguardAwakeExcludingBouncerShowing = false,
+ keyguardAwake = false,
keyguardGoingAway = false,
listeningForFaceAssistant = false,
occludingAppRequestingFaceAuth = false,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index e7e3f34..12d3d42 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -213,8 +213,6 @@
mBiometricEnabledCallbackArgCaptor;
@Captor
private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor;
- @Captor
- private ArgumentCaptor<CancellationSignal> mCancellationSignalCaptor;
// Direct executor
private final Executor mBackgroundExecutor = Runnable::run;
@@ -597,13 +595,11 @@
@Test
public void testTriesToAuthenticate_whenBouncer() {
- fingerprintIsNotEnrolled();
- faceAuthEnabled();
setKeyguardBouncerVisibility(true);
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
- verify(mFaceManager, atLeastOnce()).isHardwareDetected();
- verify(mFaceManager, atLeastOnce()).hasEnrolledTemplates(anyInt());
+ verify(mFaceManager).isHardwareDetected();
+ verify(mFaceManager).hasEnrolledTemplates(anyInt());
}
@Test
@@ -1238,9 +1234,7 @@
public void testShouldListenForFace_whenFaceIsAlreadyAuthenticated_returnsFalse()
throws RemoteException {
// Face auth should run when the following is true.
- faceAuthEnabled();
bouncerFullyVisibleAndNotGoingToSleep();
- fingerprintIsNotEnrolled();
keyguardNotGoingAway();
currentUserIsPrimary();
strongAuthNotRequired();
@@ -1267,7 +1261,7 @@
mKeyguardUpdateMonitor =
new TestableKeyguardUpdateMonitor(mSpiedContext);
- // Preconditions for face auth to run
+ // Face auth should run when the following is true.
keyguardNotGoingAway();
bouncerFullyVisibleAndNotGoingToSleep();
strongAuthNotRequired();
@@ -1284,7 +1278,7 @@
@Test
public void testShouldListenForFace_whenStrongAuthDoesNotAllowScanning_returnsFalse()
throws RemoteException {
- // Preconditions for face auth to run
+ // Face auth should run when the following is true.
keyguardNotGoingAway();
bouncerFullyVisibleAndNotGoingToSleep();
currentUserIsPrimary();
@@ -1305,11 +1299,8 @@
@Test
public void testShouldListenForFace_whenBiometricsDisabledForUser_returnsFalse()
throws RemoteException {
- // Preconditions for face auth to run
- faceAuthEnabled();
keyguardNotGoingAway();
bouncerFullyVisibleAndNotGoingToSleep();
- fingerprintIsNotEnrolled();
currentUserIsPrimary();
currentUserDoesNotHaveTrust();
biometricsNotDisabledThroughDevicePolicyManager();
@@ -1329,11 +1320,9 @@
@Test
public void testShouldListenForFace_whenUserCurrentlySwitching_returnsFalse()
throws RemoteException {
- // Preconditions for face auth to run
- faceAuthEnabled();
+ // Face auth should run when the following is true.
keyguardNotGoingAway();
bouncerFullyVisibleAndNotGoingToSleep();
- fingerprintIsNotEnrolled();
currentUserIsPrimary();
currentUserDoesNotHaveTrust();
biometricsNotDisabledThroughDevicePolicyManager();
@@ -1352,11 +1341,8 @@
@Test
public void testShouldListenForFace_whenSecureCameraLaunched_returnsFalse()
throws RemoteException {
- // Preconditions for face auth to run
- faceAuthEnabled();
keyguardNotGoingAway();
bouncerFullyVisibleAndNotGoingToSleep();
- fingerprintIsNotEnrolled();
currentUserIsPrimary();
currentUserDoesNotHaveTrust();
biometricsNotDisabledThroughDevicePolicyManager();
@@ -1375,7 +1361,7 @@
@Test
public void testShouldListenForFace_whenOccludingAppRequestsFaceAuth_returnsTrue()
throws RemoteException {
- // Preconditions for face auth to run
+ // Face auth should run when the following is true.
keyguardNotGoingAway();
bouncerFullyVisibleAndNotGoingToSleep();
currentUserIsPrimary();
@@ -1398,8 +1384,7 @@
@Test
public void testShouldListenForFace_whenBouncerShowingAndDeviceIsAwake_returnsTrue()
throws RemoteException {
- // Preconditions for face auth to run
- faceAuthEnabled();
+ // Face auth should run when the following is true.
keyguardNotGoingAway();
currentUserIsPrimary();
currentUserDoesNotHaveTrust();
@@ -1411,7 +1396,6 @@
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
bouncerFullyVisibleAndNotGoingToSleep();
- fingerprintIsNotEnrolled();
mTestableLooper.processAllMessages();
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
@@ -1420,7 +1404,7 @@
@Test
public void testShouldListenForFace_whenAuthInterruptIsActive_returnsTrue()
throws RemoteException {
- // Preconditions for face auth to run
+ // Face auth should run when the following is true.
keyguardNotGoingAway();
currentUserIsPrimary();
currentUserDoesNotHaveTrust();
@@ -1446,7 +1430,6 @@
biometricsNotDisabledThroughDevicePolicyManager();
biometricsEnabledForCurrentUser();
userNotCurrentlySwitching();
- bouncerFullyVisible();
statusBarShadeIsLocked();
mTestableLooper.processAllMessages();
@@ -1460,9 +1443,6 @@
keyguardIsVisible();
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
statusBarShadeIsNotLocked();
- assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
- bouncerNotFullyVisible();
-
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
}
@@ -1524,44 +1504,6 @@
}
@Test
- public void testBouncerVisibility_whenBothFingerprintAndFaceIsEnrolled_stopsFaceAuth()
- throws RemoteException {
- // Both fingerprint and face are enrolled by default
- // Preconditions for face auth to run
- keyguardNotGoingAway();
- currentUserIsPrimary();
- currentUserDoesNotHaveTrust();
- biometricsNotDisabledThroughDevicePolicyManager();
- biometricsEnabledForCurrentUser();
- userNotCurrentlySwitching();
- deviceNotGoingToSleep();
- deviceIsInteractive();
- statusBarShadeIsNotLocked();
- keyguardIsVisible();
-
- mTestableLooper.processAllMessages();
- clearInvocations(mUiEventLogger);
-
- assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-
- mKeyguardUpdateMonitor.requestFaceAuth(true,
- FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
-
- verify(mFaceManager).authenticate(any(),
- mCancellationSignalCaptor.capture(),
- mAuthenticationCallbackCaptor.capture(),
- any(),
- anyInt(),
- anyBoolean());
- CancellationSignal cancelSignal = mCancellationSignalCaptor.getValue();
-
- bouncerFullyVisible();
- mTestableLooper.processAllMessages();
-
- assertThat(cancelSignal.isCanceled()).isTrue();
- }
-
- @Test
public void testFingerprintCanAuth_whenCancellationNotReceivedAndAuthFailed() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
@@ -1624,21 +1566,6 @@
.onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, "");
}
- private void faceAuthEnabled() {
- // this ensures KeyguardUpdateMonitor updates the cached mIsFaceEnrolled flag using the
- // face manager mock wire-up in setup()
- mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(mCurrentUserId);
- }
-
- private void fingerprintIsNotEnrolled() {
- when(mFingerprintManager.hasEnrolledTemplates(mCurrentUserId)).thenReturn(false);
- // This updates the cached fingerprint state.
- // There is no straightforward API to update the fingerprint state.
- // It currently works updates after enrollment changes because something else invokes
- // startListeningForFingerprint(), which internally calls this method.
- mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(mCurrentUserId);
- }
-
private void statusBarShadeIsNotLocked() {
mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
}
@@ -1745,10 +1672,6 @@
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
}
- private void bouncerNotFullyVisible() {
- setKeyguardBouncerVisibility(false);
- }
-
private void bouncerFullyVisible() {
setKeyguardBouncerVisibility(true);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
index bc94440..522b5b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
@@ -16,17 +16,28 @@
package com.android.systemui.dreams.complication;
-import static org.mockito.Mockito.verify;
+import static com.android.systemui.flags.Flags.DREAM_MEDIA_TAP_TO_OPEN;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.PendingIntent;
+import android.content.Intent;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
import androidx.test.filters.SmallTest;
+import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.media.MediaCarouselController;
import com.android.systemui.media.dream.MediaDreamComplication;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Test;
@@ -48,21 +59,52 @@
@Mock
private MediaDreamComplication mMediaComplication;
+ @Mock
+ private MediaCarouselController mMediaCarouselController;
+
+ @Mock
+ private ActivityStarter mActivityStarter;
+
+ @Mock
+ private ActivityIntentHelper mActivityIntentHelper;
+
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
+
+ @Mock
+ private NotificationLockscreenUserManager mLockscreenUserManager;
+
+ @Mock
+ private FeatureFlags mFeatureFlags;
+
+ @Mock
+ private PendingIntent mPendingIntent;
+
+ private final Intent mIntent = new Intent("android.test.TEST_ACTION");
+ private final Integer mCurrentUserId = 99;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ when(mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN)).thenReturn(false);
}
/**
* Ensures clicking media entry chip adds/removes media complication.
*/
@Test
- public void testClick() {
+ public void testClickToOpenUMO() {
final DreamMediaEntryComplication.DreamMediaEntryViewController viewController =
new DreamMediaEntryComplication.DreamMediaEntryViewController(
mView,
mDreamOverlayStateController,
- mMediaComplication);
+ mMediaComplication,
+ mMediaCarouselController,
+ mActivityStarter,
+ mActivityIntentHelper,
+ mKeyguardStateController,
+ mLockscreenUserManager,
+ mFeatureFlags);
final ArgumentCaptor<View.OnClickListener> clickListenerCaptor =
ArgumentCaptor.forClass(View.OnClickListener.class);
@@ -85,10 +127,90 @@
new DreamMediaEntryComplication.DreamMediaEntryViewController(
mView,
mDreamOverlayStateController,
- mMediaComplication);
+ mMediaComplication,
+ mMediaCarouselController,
+ mActivityStarter,
+ mActivityIntentHelper,
+ mKeyguardStateController,
+ mLockscreenUserManager,
+ mFeatureFlags);
viewController.onViewDetached();
verify(mView).setSelected(false);
verify(mDreamOverlayStateController).removeComplication(mMediaComplication);
}
+
+ /**
+ * Ensures clicking media entry chip opens media when flag is set.
+ */
+ @Test
+ public void testClickToOpenMediaOverLockscreen() {
+ when(mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN)).thenReturn(true);
+
+ when(mMediaCarouselController.getCurrentVisibleMediaContentIntent()).thenReturn(
+ mPendingIntent);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mPendingIntent.getIntent()).thenReturn(mIntent);
+ when(mLockscreenUserManager.getCurrentUserId()).thenReturn(mCurrentUserId);
+
+ final DreamMediaEntryComplication.DreamMediaEntryViewController viewController =
+ new DreamMediaEntryComplication.DreamMediaEntryViewController(
+ mView,
+ mDreamOverlayStateController,
+ mMediaComplication,
+ mMediaCarouselController,
+ mActivityStarter,
+ mActivityIntentHelper,
+ mKeyguardStateController,
+ mLockscreenUserManager,
+ mFeatureFlags);
+ viewController.onViewAttached();
+
+ final ArgumentCaptor<View.OnClickListener> clickListenerCaptor =
+ ArgumentCaptor.forClass(View.OnClickListener.class);
+ verify(mView).setOnClickListener(clickListenerCaptor.capture());
+
+ when(mActivityIntentHelper.wouldShowOverLockscreen(mIntent, mCurrentUserId)).thenReturn(
+ true);
+
+ clickListenerCaptor.getValue().onClick(mView);
+ verify(mActivityStarter).startActivity(mIntent, true, null, true);
+ }
+
+ /**
+ * Ensures clicking media entry chip opens media when flag is set.
+ */
+ @Test
+ public void testClickToOpenMediaDismissingLockscreen() {
+ when(mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN)).thenReturn(true);
+
+ when(mMediaCarouselController.getCurrentVisibleMediaContentIntent()).thenReturn(
+ mPendingIntent);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mPendingIntent.getIntent()).thenReturn(mIntent);
+ when(mLockscreenUserManager.getCurrentUserId()).thenReturn(mCurrentUserId);
+
+ final DreamMediaEntryComplication.DreamMediaEntryViewController viewController =
+ new DreamMediaEntryComplication.DreamMediaEntryViewController(
+ mView,
+ mDreamOverlayStateController,
+ mMediaComplication,
+ mMediaCarouselController,
+ mActivityStarter,
+ mActivityIntentHelper,
+ mKeyguardStateController,
+ mLockscreenUserManager,
+ mFeatureFlags);
+ viewController.onViewAttached();
+
+ final ArgumentCaptor<View.OnClickListener> clickListenerCaptor =
+ ArgumentCaptor.forClass(View.OnClickListener.class);
+ verify(mView).setOnClickListener(clickListenerCaptor.capture());
+
+ when(mActivityIntentHelper.wouldShowOverLockscreen(mIntent, mCurrentUserId)).thenReturn(
+ false);
+
+ clickListenerCaptor.getValue().onClick(mView);
+ verify(mActivityStarter).postStartActivityDismissingKeyguard(mPendingIntent, null);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
index 5dd1cfc..e3e3b74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media
+import android.app.PendingIntent
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
@@ -43,6 +44,7 @@
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
@@ -366,7 +368,7 @@
playerIndex,
mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
)
- assertEquals( playerIndex, 0)
+ assertEquals(playerIndex, 0)
// Replaying the same media player one more time.
// And check that the card stays in its position.
@@ -402,4 +404,44 @@
visualStabilityCallback.value.onReorderingAllowed()
assertEquals(true, result)
}
+
+ @Test
+ fun testGetCurrentVisibleMediaContentIntent() {
+ val clickIntent1 = mock(PendingIntent::class.java)
+ val player1 = Triple("player1",
+ DATA.copy(clickIntent = clickIntent1),
+ 1000L)
+ clock.setCurrentTimeMillis(player1.third)
+ MediaPlayerData.addMediaPlayer(player1.first,
+ player1.second.copy(notificationKey = player1.first),
+ panel, clock, isSsReactivated = false)
+
+ assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent1)
+
+ val clickIntent2 = mock(PendingIntent::class.java)
+ val player2 = Triple("player2",
+ DATA.copy(clickIntent = clickIntent2),
+ 2000L)
+ clock.setCurrentTimeMillis(player2.third)
+ MediaPlayerData.addMediaPlayer(player2.first,
+ player2.second.copy(notificationKey = player2.first),
+ panel, clock, isSsReactivated = false)
+
+ // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
+ // added to the front because it was active more recently.
+ assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
+
+ val clickIntent3 = mock(PendingIntent::class.java)
+ val player3 = Triple("player3",
+ DATA.copy(clickIntent = clickIntent3),
+ 500L)
+ clock.setCurrentTimeMillis(player3.third)
+ MediaPlayerData.addMediaPlayer(player3.first,
+ player3.second.copy(notificationKey = player3.first),
+ panel, clock, isSsReactivated = false)
+
+ // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
+ // added to the end because it was active less recently.
+ assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
index 0bfc034..2f52950 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
@@ -16,7 +16,7 @@
package com.android.systemui.media.dream;
-import static com.android.systemui.flags.Flags.MEDIA_DREAM_COMPLICATION;
+import static com.android.systemui.flags.Flags.DREAM_MEDIA_COMPLICATION;
import static org.mockito.AdditionalMatchers.not;
import static org.mockito.ArgumentMatchers.any;
@@ -68,7 +68,7 @@
public void setup() {
MockitoAnnotations.initMocks(this);
- when(mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)).thenReturn(true);
+ when(mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)).thenReturn(true);
}
@Test
@@ -137,7 +137,7 @@
@Test
public void testOnMediaDataLoaded_mediaComplicationDisabled_doesNotAddComplication() {
- when(mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)).thenReturn(false);
+ when(mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)).thenReturn(false);
final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager,
mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a4fc160..3aed167 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -147,7 +147,8 @@
"android.hardware.boot-V1.0-java",
"android.hardware.boot-V1.1-java",
"android.hardware.boot-V1.2-java",
- "android.hardware.broadcastradio-V2.0-java",
+ "android.hardware.broadcastradio-V2.0-java", // HIDL
+ "android.hardware.broadcastradio-V1-java", // AIDL
"android.hardware.health-V1.0-java", // HIDL
"android.hardware.health-V2.0-java", // HIDL
"android.hardware.health-V2.1-java", // HIDL
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index b96d33c..4278b3e 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -22,9 +22,12 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.database.ContentObserver;
import android.hardware.health.HealthInfo;
import android.hardware.health.V2_1.BatteryCapacityLevel;
@@ -185,6 +188,17 @@
private ArrayDeque<Bundle> mBatteryLevelsEventQueue;
private long mLastBatteryLevelChangedSentMs;
+ private Bundle mBatteryChangedOptions = BroadcastOptions.makeRemovingMatchingFilter(
+ new IntentFilter(Intent.ACTION_BATTERY_CHANGED)).toBundle();
+ private Bundle mPowerConnectedOptions = BroadcastOptions.makeRemovingMatchingFilter(
+ new IntentFilter(Intent.ACTION_POWER_DISCONNECTED)).toBundle();
+ private Bundle mPowerDisconnectedOptions = BroadcastOptions.makeRemovingMatchingFilter(
+ new IntentFilter(Intent.ACTION_POWER_CONNECTED)).toBundle();
+ private Bundle mBatteryLowOptions = BroadcastOptions.makeRemovingMatchingFilter(
+ new IntentFilter(Intent.ACTION_BATTERY_OKAY)).toBundle();
+ private Bundle mBatteryOkayOptions = BroadcastOptions.makeRemovingMatchingFilter(
+ new IntentFilter(Intent.ACTION_BATTERY_LOW)).toBundle();
+
private MetricsLogger mMetricsLogger;
public BatteryService(Context context) {
@@ -606,7 +620,8 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+ mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+ mPowerConnectedOptions);
}
});
}
@@ -617,7 +632,8 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+ mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+ mPowerDisconnectedOptions);
}
});
}
@@ -630,7 +646,8 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+ mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+ mBatteryLowOptions);
}
});
} else if (mSentLowBatteryBroadcast &&
@@ -642,7 +659,8 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+ mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+ mBatteryOkayOptions);
}
});
}
@@ -712,7 +730,8 @@
+ ", info:" + mHealthInfo.toString());
}
- mHandler.post(() -> ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL));
+ mHandler.post(() -> ActivityManager.broadcastStickyIntent(intent, AppOpsManager.OP_NONE,
+ mBatteryChangedOptions, UserHandle.USER_ALL));
}
private void sendBatteryLevelChangedIntentLocked() {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9d24e8e..50b47a6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -245,6 +245,7 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionInfo;
+import android.content.pm.PermissionMethod;
import android.content.pm.ProcessInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ProviderInfoList;
@@ -4515,7 +4516,7 @@
// Clean-up disabled broadcast receivers.
for (int i = mBroadcastQueues.length - 1; i >= 0; i--) {
mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
- packageName, disabledClasses, userId, true);
+ packageName, disabledClasses, userId);
}
}
@@ -4524,7 +4525,7 @@
boolean didSomething = false;
for (int i = mBroadcastQueues.length - 1; i >= 0; i--) {
didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
- null, null, userId, true);
+ null, null, userId);
}
return didSomething;
}
@@ -4660,7 +4661,7 @@
if (doit) {
for (i = mBroadcastQueues.length - 1; i >= 0; i--) {
didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
- packageName, null, userId, doit);
+ packageName, null, userId);
}
}
@@ -5963,6 +5964,12 @@
}
}
+ /**
+ * Allows if {@code pid} is {@link #MY_PID}, then denies if the {@code pid} has been denied
+ * provided non-{@code null} {@code permission} before. Otherwise calls into
+ * {@link ActivityManager#checkComponentPermission(String, int, int, boolean)}.
+ */
+ @PermissionMethod
public static int checkComponentPermission(String permission, int pid, int uid,
int owningUid, boolean exported) {
if (pid == MY_PID) {
@@ -6009,6 +6016,7 @@
* This can be called with or without the global lock held.
*/
@Override
+ @PermissionMethod
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
return PackageManager.PERMISSION_DENIED;
@@ -6020,6 +6028,7 @@
* Binder IPC calls go through the public entry point.
* This can be called with or without the global lock held.
*/
+ @PermissionMethod
int checkCallingPermission(String permission) {
return checkPermission(permission,
Binder.getCallingPid(),
@@ -6029,6 +6038,7 @@
/**
* This can be called with or without the global lock held.
*/
+ @PermissionMethod
void enforceCallingPermission(String permission, String func) {
if (checkCallingPermission(permission)
== PackageManager.PERMISSION_GRANTED) {
@@ -6046,6 +6056,7 @@
/**
* This can be called with or without the global lock held.
*/
+ @PermissionMethod
void enforcePermission(String permission, int pid, int uid, String func) {
if (checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED) {
return;
@@ -17355,6 +17366,8 @@
bOptions.setTemporaryAppAllowlist(mInternal.getBootTimeTempAllowListDuration(),
TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
PowerExemptionManager.REASON_LOCALE_CHANGED, "");
+ bOptions.setRemoveMatchingFilter(
+ new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
null, null, OP_NONE, bOptions.toBundle(), false, false, MY_PID,
SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(),
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 2ebe0b4..3efb628 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.compat.annotation.Overridable;
@@ -24,6 +25,8 @@
import android.database.ContentObserver;
import android.os.Build;
import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.util.KeyValueListParser;
import android.util.Slog;
@@ -39,6 +42,9 @@
public class BroadcastConstants {
private static final String TAG = "BroadcastConstants";
+ // TODO: migrate remaining constants to be loaded from DeviceConfig
+ // TODO: migrate fg/bg values into single constants instance
+
// Value element names within the Settings record
static final String KEY_TIMEOUT = "bcast_timeout";
static final String KEY_SLOW_TIME = "bcast_slow_time";
@@ -115,6 +121,35 @@
// started its process can start a background activity.
public long ALLOW_BG_ACTIVITY_START_TIMEOUT = DEFAULT_ALLOW_BG_ACTIVITY_START_TIMEOUT;
+ /**
+ * For {@link BroadcastQueueModernImpl}: Maximum number of process queues to
+ * dispatch broadcasts to simultaneously.
+ */
+ public int MAX_RUNNING_PROCESS_QUEUES = DEFAULT_MAX_RUNNING_PROCESS_QUEUES;
+ private static final int DEFAULT_MAX_RUNNING_PROCESS_QUEUES = 4;
+
+ /**
+ * For {@link BroadcastQueueModernImpl}: Maximum number of active broadcasts
+ * to dispatch to a "running" process queue before we retire them back to
+ * being "runnable" to give other processes a chance to run.
+ */
+ public int MAX_RUNNING_ACTIVE_BROADCASTS = DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS;
+ private static final int DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS = 16;
+
+ /**
+ * For {@link BroadcastQueueModernImpl}: Default delay to apply to normal
+ * broadcasts, giving a chance for debouncing of rapidly changing events.
+ */
+ public long DELAY_NORMAL_MILLIS = DEFAULT_DELAY_NORMAL_MILLIS;
+ private static final long DEFAULT_DELAY_NORMAL_MILLIS = 10_000 * Build.HW_TIMEOUT_MULTIPLIER;
+
+ /**
+ * For {@link BroadcastQueueModernImpl}: Default delay to apply to
+ * broadcasts targeting cached applications.
+ */
+ public long DELAY_CACHED_MILLIS = DEFAULT_DELAY_CACHED_MILLIS;
+ private static final long DEFAULT_DELAY_CACHED_MILLIS = 30_000 * Build.HW_TIMEOUT_MULTIPLIER;
+
// Settings override tracking for this instance
private String mSettingsKey;
private SettingsObserver mSettingsObserver;
@@ -128,7 +163,7 @@
@Override
public void onChange(boolean selfChange) {
- updateConstants();
+ updateSettingsConstants();
}
}
@@ -148,11 +183,15 @@
mSettingsObserver = new SettingsObserver(handler);
mResolver.registerContentObserver(Settings.Global.getUriFor(mSettingsKey),
false, mSettingsObserver);
+ updateSettingsConstants();
- updateConstants();
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ new HandlerExecutor(handler), this::updateDeviceConfigConstants);
+ updateDeviceConfigConstants(
+ DeviceConfig.getProperties(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER));
}
- private void updateConstants() {
+ private void updateSettingsConstants() {
synchronized (mParser) {
try {
mParser.setString(Settings.Global.getString(mResolver, mSettingsKey));
@@ -173,6 +212,17 @@
}
}
+ private void updateDeviceConfigConstants(@NonNull DeviceConfig.Properties properties) {
+ MAX_RUNNING_PROCESS_QUEUES = properties.getInt("bcast_max_running_process_queues",
+ DEFAULT_MAX_RUNNING_PROCESS_QUEUES);
+ MAX_RUNNING_ACTIVE_BROADCASTS = properties.getInt("bcast_max_running_active_broadcasts",
+ DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS);
+ DELAY_NORMAL_MILLIS = properties.getLong("bcast_delay_normal_millis",
+ DEFAULT_DELAY_NORMAL_MILLIS);
+ DELAY_CACHED_MILLIS = properties.getLong("bcast_delay_cached_millis",
+ DEFAULT_DELAY_CACHED_MILLIS);
+ }
+
/**
* Standard dumpsys support; invoked from BroadcastQueue dump
*/
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index f9fcc9e..77eefb4 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UptimeMillisLong;
+import android.os.Trace;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
@@ -28,6 +29,8 @@
import com.android.internal.os.SomeArgs;
import java.util.ArrayDeque;
+import java.util.Iterator;
+import java.util.Objects;
/**
* Queue of pending {@link BroadcastRecord} entries intended for delivery to a
@@ -40,21 +43,13 @@
* Internally each queue consists of a pending broadcasts which are waiting to
* be dispatched, and a single active broadcast which is currently being
* dispatched.
+ * <p>
+ * This entire class is marked as {@code NotThreadSafe} since it's the
+ * responsibility of the caller to always interact with a relevant lock held.
*/
+// @NotThreadSafe
class BroadcastProcessQueue {
- /**
- * Default delay to apply to background broadcasts, giving a chance for
- * debouncing of rapidly changing events.
- */
- // TODO: shift hard-coded defaults to BroadcastConstants
- private static final long DELAY_DEFAULT_MILLIS = 10_000;
-
- /**
- * Default delay to apply to broadcasts targeting cached applications.
- */
- // TODO: shift hard-coded defaults to BroadcastConstants
- private static final long DELAY_CACHED_MILLIS = 30_000;
-
+ final @NonNull BroadcastConstants constants;
final @NonNull String processName;
final int uid;
@@ -78,6 +73,11 @@
@Nullable ProcessRecord app;
/**
+ * Track name to use for {@link Trace} events.
+ */
+ @Nullable String traceTrackName;
+
+ /**
* Ordered collection of broadcasts that are waiting to be dispatched to
* this process, as a pair of {@link BroadcastRecord} and the index into
* {@link BroadcastRecord#receivers} that represents the receiver.
@@ -102,6 +102,12 @@
private int mActiveCountSinceIdle;
/**
+ * Flag indicating that the currently active broadcast is being dispatched
+ * was scheduled via a cold start.
+ */
+ private boolean mActiveViaColdStart;
+
+ /**
* Count of {@link #mPending} broadcasts of these various flavors.
*/
private int mCountForeground;
@@ -113,8 +119,10 @@
private boolean mProcessCached;
- public BroadcastProcessQueue(@NonNull String processName, int uid) {
- this.processName = processName;
+ public BroadcastProcessQueue(@NonNull BroadcastConstants constants,
+ @NonNull String processName, int uid) {
+ this.constants = Objects.requireNonNull(constants);
+ this.processName = Objects.requireNonNull(processName);
this.uid = uid;
}
@@ -148,6 +156,51 @@
}
/**
+ * Functional interface that tests a {@link BroadcastRecord} that has been
+ * previously enqueued in {@link BroadcastProcessQueue}.
+ */
+ @FunctionalInterface
+ public interface BroadcastPredicate {
+ public boolean test(@NonNull BroadcastRecord r, int index);
+ }
+
+ /**
+ * Functional interface that consumes a {@link BroadcastRecord} that has
+ * been previously enqueued in {@link BroadcastProcessQueue}.
+ */
+ @FunctionalInterface
+ public interface BroadcastConsumer {
+ public void accept(@NonNull BroadcastRecord r, int index);
+ }
+
+ /**
+ * Remove any broadcasts matching the given predicate.
+ * <p>
+ * Predicates that choose to remove a broadcast <em>must</em> finish
+ * delivery of the matched broadcast, to ensure that situations like ordered
+ * broadcasts are handled consistently.
+ */
+ public boolean removeMatchingBroadcasts(@NonNull BroadcastPredicate predicate,
+ @NonNull BroadcastConsumer consumer) {
+ boolean didSomething = false;
+ final Iterator<SomeArgs> it = mPending.iterator();
+ while (it.hasNext()) {
+ final SomeArgs args = it.next();
+ final BroadcastRecord record = (BroadcastRecord) args.arg1;
+ final int index = args.argi1;
+ if (predicate.test(record, index)) {
+ consumer.accept(record, index);
+ args.recycle();
+ it.remove();
+ didSomething = true;
+ }
+ }
+ // TODO: also check any active broadcast once we have a better "nonce"
+ // representing each scheduled broadcast to avoid races
+ return didSomething;
+ }
+
+ /**
* Update if this process is in the "cached" state, typically signaling that
* broadcast dispatch should be paused or delayed.
*/
@@ -187,6 +240,14 @@
return mActiveCountSinceIdle;
}
+ public void setActiveViaColdStart(boolean activeViaColdStart) {
+ mActiveViaColdStart = activeViaColdStart;
+ }
+
+ public boolean getActiveViaColdStart() {
+ return mActiveViaColdStart;
+ }
+
/**
* Set the currently active broadcast to the next pending broadcast.
*/
@@ -197,6 +258,7 @@
mActive = (BroadcastRecord) next.arg1;
mActiveIndex = next.argi1;
mActiveCountSinceIdle++;
+ mActiveViaColdStart = false;
next.recycle();
if (mActive.isForeground()) {
mCountForeground--;
@@ -217,21 +279,55 @@
mActive = null;
mActiveIndex = 0;
mActiveCountSinceIdle = 0;
+ mActiveViaColdStart = false;
}
- public void setActiveDeliveryState(int deliveryState) {
- checkState(isActive(), "isActive");
- mActive.setDeliveryState(mActiveIndex, deliveryState);
+ public void traceProcessStartingBegin() {
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ traceTrackName, toShortString() + " starting", hashCode());
}
+ public void traceProcessRunningBegin() {
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ traceTrackName, toShortString() + " running", hashCode());
+ }
+
+ public void traceProcessEnd() {
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ traceTrackName, hashCode());
+ }
+
+ public void traceActiveBegin() {
+ final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ traceTrackName, mActive.toShortString() + " scheduled", cookie);
+ }
+
+ public void traceActiveEnd() {
+ final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ traceTrackName, cookie);
+ }
+
+ /**
+ * Return the broadcast being actively dispatched in this process.
+ */
public @NonNull BroadcastRecord getActive() {
checkState(isActive(), "isActive");
return mActive;
}
- public @NonNull Object getActiveReceiver() {
+ /**
+ * Return the index into {@link BroadcastRecord#receivers} of the receiver
+ * being actively dispatched in this process.
+ */
+ public int getActiveIndex() {
checkState(isActive(), "isActive");
- return mActive.receivers.get(mActiveIndex);
+ return mActiveIndex;
+ }
+
+ public boolean isEmpty() {
+ return (mActive != null) && mPending.isEmpty();
}
public boolean isActive() {
@@ -257,7 +353,7 @@
return mRunnableAt;
}
- private void invalidateRunnableAt() {
+ public void invalidateRunnableAt() {
mRunnableAtInvalidated = true;
}
@@ -267,7 +363,17 @@
private void updateRunnableAt() {
final SomeArgs next = mPending.peekFirst();
if (next != null) {
- final long runnableAt = ((BroadcastRecord) next.arg1).enqueueTime;
+ final BroadcastRecord r = (BroadcastRecord) next.arg1;
+ final int index = next.argi1;
+
+ // If our next broadcast is ordered, and we're not the next receiver
+ // in line, then we're not runnable at all
+ if (r.ordered && r.finishedCount != index) {
+ mRunnableAt = Long.MAX_VALUE;
+ return;
+ }
+
+ final long runnableAt = r.enqueueTime;
if (mCountForeground > 0) {
mRunnableAt = runnableAt;
} else if (mCountOrdered > 0) {
@@ -275,9 +381,9 @@
} else if (mCountAlarm > 0) {
mRunnableAt = runnableAt;
} else if (mProcessCached) {
- mRunnableAt = runnableAt + DELAY_CACHED_MILLIS;
+ mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS;
} else {
- mRunnableAt = runnableAt + DELAY_DEFAULT_MILLIS;
+ mRunnableAt = runnableAt + constants.DELAY_NORMAL_MILLIS;
}
} else {
mRunnableAt = Long.MAX_VALUE;
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index b1be022..972a1ce 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -147,7 +147,7 @@
*/
@GuardedBy("mService")
public abstract boolean cleanupDisabledPackageReceiversLocked(@Nullable String packageName,
- @Nullable Set<String> filterByClasses, int userId, boolean doit);
+ @Nullable Set<String> filterByClasses, int userId);
/**
* Quickly determine if this queue has broadcasts that are still waiting to
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index a980db1..28bd9c3 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -46,7 +46,6 @@
import android.app.IApplicationThread;
import android.app.RemoteServiceException.CannotDeliverBroadcastException;
import android.app.usage.UsageEvents.Event;
-import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.IIntentReceiver;
@@ -1447,7 +1446,7 @@
return null;
}
- private void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
+ static void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
// Only log after last receiver.
// In case of split BOOT_COMPLETED broadcast, make sure only call this method on the
// last BroadcastRecord of the split broadcast which has non-null resultTo.
@@ -1509,19 +1508,12 @@
if (targetPackage == null) {
return;
}
- getUsageStatsManagerInternal().reportBroadcastDispatched(
+ mService.mUsageStatsService.reportBroadcastDispatched(
r.callingUid, targetPackage, UserHandle.of(r.userId),
r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime(),
mService.getUidStateLocked(targetUid));
}
- @NonNull
- private UsageStatsManagerInternal getUsageStatsManagerInternal() {
- final UsageStatsManagerInternal usageStatsManagerInternal =
- LocalServices.getService(UsageStatsManagerInternal.class);
- return usageStatsManagerInternal;
- }
-
private void maybeAddAllowBackgroundActivityStartsToken(ProcessRecord proc, BroadcastRecord r) {
if (r == null || proc == null || !r.allowBackgroundActivityStarts) {
return;
@@ -1693,18 +1685,15 @@
}
public boolean cleanupDisabledPackageReceiversLocked(
- String packageName, Set<String> filterByClasses, int userId, boolean doit) {
+ String packageName, Set<String> filterByClasses, int userId) {
boolean didSomething = false;
for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
didSomething |= mParallelBroadcasts.get(i).cleanupDisabledPackageReceiversLocked(
- packageName, filterByClasses, userId, doit);
- if (!doit && didSomething) {
- return true;
- }
+ packageName, filterByClasses, userId, true);
}
didSomething |= mDispatcher.cleanupDisabledPackageReceiversLocked(packageName,
- filterByClasses, userId, doit);
+ filterByClasses, userId, true);
return didSomething;
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 8dfb22e..a36a9f6 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -19,11 +19,21 @@
import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
+import static com.android.server.am.BroadcastRecord.deliveryStateToString;
+import static com.android.server.am.BroadcastRecord.getReceiverPackageName;
import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
import static com.android.server.am.BroadcastRecord.getReceiverUid;
+import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
+import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER;
import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
import android.annotation.NonNull;
@@ -32,16 +42,23 @@
import android.app.IApplicationThread;
import android.app.RemoteServiceException.CannotDeliverBroadcastException;
import android.app.UidObserver;
+import android.app.usage.UsageEvents.Event;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
@@ -49,7 +66,12 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
import com.android.internal.os.TimeoutRecord;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.am.BroadcastProcessQueue.BroadcastConsumer;
+import com.android.server.am.BroadcastProcessQueue.BroadcastPredicate;
import com.android.server.am.BroadcastRecord.DeliveryState;
import java.io.FileDescriptor;
@@ -59,6 +81,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.function.Predicate;
/**
* Alternative {@link BroadcastQueue} implementation which pivots broadcasts to
@@ -68,6 +91,21 @@
* {@link BroadcastProcessQueue} instance. Each queue has a concept of being
* "runnable at" a particular time in the future, which supports arbitrarily
* pausing or delaying delivery on a per-process basis.
+ * <p>
+ * To keep things easy to reason about, there is a <em>very strong</em>
+ * preference to have broadcast interactions flow through a consistent set of
+ * methods in this specific order:
+ * <ol>
+ * <li>{@link #updateRunnableList} promotes a per-process queue to be runnable
+ * when it has relevant pending broadcasts
+ * <li>{@link #updateRunningList} promotes a runnable queue to be running and
+ * schedules delivery of the first broadcast
+ * <li>{@link #scheduleReceiverColdLocked} requests any needed cold-starts, and
+ * results are reported back via {@link #onApplicationAttachedLocked}
+ * <li>{@link #scheduleReceiverWarmLocked} requests dispatch of the currently
+ * active broadcast to a running app, and results are reported back via
+ * {@link #finishReceiverLocked}
+ * </ol>
*/
class BroadcastQueueModernImpl extends BroadcastQueue {
BroadcastQueueModernImpl(ActivityManagerService service, Handler handler,
@@ -80,40 +118,27 @@
BroadcastConstants fgConstants, BroadcastConstants bgConstants,
BroadcastSkipPolicy skipPolicy, BroadcastHistory history) {
super(service, handler, "modern", skipPolicy, history);
+
+ // For the moment, read agnostic constants from foreground
+ mConstants = Objects.requireNonNull(fgConstants);
mFgConstants = Objects.requireNonNull(fgConstants);
mBgConstants = Objects.requireNonNull(bgConstants);
+
mLocalHandler = new Handler(handler.getLooper(), mLocalCallback);
+
+ // We configure runnable size only once at boot; it'd be too complex to
+ // try resizing dynamically at runtime
+ mRunning = new BroadcastProcessQueue[mConstants.MAX_RUNNING_PROCESS_QUEUES];
}
- // TODO: add support for ordered broadcasts
// TODO: add support for replacing pending broadcasts
// TODO: add support for merging pending broadcasts
- // TODO: add trace points for debugging broadcast flows
- // TODO: record broadcast state change timing statistics
- // TODO: record historical broadcast statistics
+ // TODO: consider reordering foreground broadcasts within queue
- // TODO: pause queues for apps involved in backup/restore
// TODO: pause queues when background services are running
// TODO: pause queues when processes are frozen
- // TODO: clean up queues for removed apps
-
- /**
- * Maximum number of process queues to dispatch broadcasts to
- * simultaneously.
- */
- // TODO: shift hard-coded defaults to BroadcastConstants
- private static final int MAX_RUNNING_PROCESS_QUEUES = 4;
-
- /**
- * Maximum number of active broadcasts to dispatch to a "running" process
- * queue before we retire them back to being "runnable" to give other
- * processes a chance to run.
- */
- // TODO: shift hard-coded defaults to BroadcastConstants
- private static final int MAX_RUNNING_ACTIVE_BROADCASTS = 16;
-
/**
* Map from UID to per-process broadcast queues. If a UID hosts more than
* one process, each additional process is stored as a linked list using
@@ -136,11 +161,14 @@
private BroadcastProcessQueue mRunnableHead = null;
/**
- * Collection of queues which are "running". This will never be larger than
- * {@link #MAX_RUNNING_PROCESS_QUEUES}.
+ * Array of queues which are currently "running", which may have gaps that
+ * are {@code null}.
+ *
+ * @see #getRunningSize
+ * @see #getRunningIndexOf
*/
@GuardedBy("mService")
- private final ArrayList<BroadcastProcessQueue> mRunning = new ArrayList<>();
+ private final BroadcastProcessQueue[] mRunning;
/**
* Single queue which is "running" but is awaiting a cold start to be
@@ -156,11 +184,13 @@
@GuardedBy("mService")
private final ArrayList<CountDownLatch> mWaitingForIdle = new ArrayList<>();
+ private final BroadcastConstants mConstants;
private final BroadcastConstants mFgConstants;
private final BroadcastConstants mBgConstants;
private static final int MSG_UPDATE_RUNNING_LIST = 1;
private static final int MSG_DELIVERY_TIMEOUT = 2;
+ private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 3;
private void enqueueUpdateRunningList() {
mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
@@ -184,11 +214,44 @@
}
return true;
}
+ case MSG_BG_ACTIVITY_START_TIMEOUT: {
+ synchronized (mService) {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final ProcessRecord app = (ProcessRecord) args.arg1;
+ final BroadcastRecord r = (BroadcastRecord) args.arg2;
+ args.recycle();
+ app.removeAllowBackgroundActivityStartsToken(r);
+ }
+ return true;
+ }
}
return false;
};
/**
+ * Return the total number of active queues contained inside
+ * {@link #mRunning}.
+ */
+ private int getRunningSize() {
+ int size = 0;
+ for (int i = 0; i < mRunning.length; i++) {
+ if (mRunning[i] != null) size++;
+ }
+ return size;
+ }
+
+ /**
+ * Return the first index of the given value contained inside
+ * {@link #mRunning}, otherwise {@code -1}.
+ */
+ private int getRunningIndexOf(@Nullable BroadcastProcessQueue test) {
+ for (int i = 0; i < mRunning.length; i++) {
+ if (mRunning[i] == test) return i;
+ }
+ return -1;
+ }
+
+ /**
* Consider updating the list of "runnable" queues, specifically with
* relation to the given queue.
* <p>
@@ -198,7 +261,7 @@
*/
@GuardedBy("mService")
private void updateRunnableList(@NonNull BroadcastProcessQueue queue) {
- if (mRunning.contains(queue)) {
+ if (getRunningIndexOf(queue) >= 0) {
// Already running; they'll be reinserted into the runnable list
// once they finish running, so no need to update them now
return;
@@ -215,9 +278,7 @@
? queue.runnableAtPrev.getRunnableAt() <= queue.getRunnableAt() : true;
final boolean nextHigher = (queue.runnableAtNext != null)
? queue.runnableAtNext.getRunnableAt() >= queue.getRunnableAt() : true;
- if (prevLower && nextHigher) {
- return;
- } else {
+ if (!prevLower || !nextHigher) {
mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
mRunnableHead = insertIntoRunnableList(mRunnableHead, queue);
}
@@ -227,20 +288,26 @@
} else if (inQueue) {
mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
}
+
+ // If app isn't running, and there's nothing in the queue, clean up
+ if (queue.isEmpty() && !queue.isProcessWarm()) {
+ removeProcessQueue(queue.processName, queue.uid);
+ }
}
/**
* Consider updating the list of "running" queues.
* <p>
* This method can promote "runnable" queues to become "running", subject to
- * a maximum of {@link #MAX_RUNNING_PROCESS_QUEUES} warm processes and only
- * one pending cold-start.
+ * a maximum of {@link BroadcastConstants#MAX_RUNNING_PROCESS_QUEUES} warm
+ * processes and only one pending cold-start.
*/
@GuardedBy("mService")
private void updateRunningList() {
- int avail = MAX_RUNNING_PROCESS_QUEUES - mRunning.size();
+ int avail = mRunning.length - getRunningSize();
if (avail == 0) return;
+ final int cookie = traceBegin(TAG, "updateRunningList");
final long now = SystemClock.uptimeMillis();
// If someone is waiting to go idle, everything is runnable now
@@ -285,23 +352,35 @@
+ " from runnable to running; process is " + queue.app);
// Allocate this available permit and start running!
- mRunning.add(queue);
+ final int queueIndex = getRunningIndexOf(null);
+ mRunning[queueIndex] = queue;
avail--;
// Remove ourselves from linked list of runnable things
mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
- queue.makeActiveNextPending();
+ // Emit all trace events for this process into a consistent track
+ queue.traceTrackName = TAG + ".mRunning[" + queueIndex + "]";
- // If we're already warm, schedule it; otherwise we'll wait for the
- // cold start to circle back around
+ // If we're already warm, boost OOM adjust now; if cold we'll boost
+ // it after the app has been started
if (processWarm) {
+ notifyStartedRunning(queue);
+ }
+
+ // If we're already warm, schedule next pending broadcast now;
+ // otherwise we'll wait for the cold start to circle back around
+ queue.makeActiveNextPending();
+ if (processWarm) {
+ queue.traceProcessRunningBegin();
scheduleReceiverWarmLocked(queue);
} else {
+ queue.traceProcessStartingBegin();
scheduleReceiverColdLocked(queue);
}
- mService.enqueueOomAdjTargetLocked(queue.app);
+ // We've moved at least one process into running state above, so we
+ // need to kick off an OOM adjustment pass
updateOomAdj = true;
// Move to considering next runnable queue
@@ -316,6 +395,8 @@
mWaitingForIdle.forEach((latch) -> latch.countDown());
mWaitingForIdle.clear();
}
+
+ traceEnd(TAG, cookie);
}
@Override
@@ -324,9 +405,13 @@
if ((mRunningColdStart != null) && (mRunningColdStart.app == app)) {
// We've been waiting for this app to cold start, and it's ready
// now; dispatch its next broadcast and clear the slot
- scheduleReceiverWarmLocked(mRunningColdStart);
+ final BroadcastProcessQueue queue = mRunningColdStart;
mRunningColdStart = null;
+ queue.traceProcessEnd();
+ queue.traceProcessRunningBegin();
+ scheduleReceiverWarmLocked(queue);
+
// We might be willing to kick off another cold start
enqueueUpdateRunningList();
didSomething = true;
@@ -366,6 +451,11 @@
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
didSomething = true;
}
+
+ // If queue has nothing else pending, consider cleaning it
+ if (queue.isEmpty()) {
+ updateRunnableList(queue);
+ }
}
return didSomething;
@@ -374,7 +464,7 @@
@Override
public int getPreferredSchedulingGroupLocked(@NonNull ProcessRecord app) {
final BroadcastProcessQueue queue = getProcessQueue(app);
- if ((queue != null) && mRunning.contains(queue)) {
+ if ((queue != null) && getRunningIndexOf(queue) >= 0) {
return queue.getPreferredSchedulingGroupLocked();
}
return ProcessList.SCHED_GROUP_UNDEFINED;
@@ -385,6 +475,17 @@
// TODO: handle empty receivers to deliver result immediately
if (r.receivers == null) return;
+ final IntentFilter removeMatchingFilter = (r.options != null)
+ ? r.options.getRemoveMatchingFilter() : null;
+ if (removeMatchingFilter != null) {
+ final Predicate<Intent> removeMatching = removeMatchingFilter.asPredicate();
+ skipMatchingBroadcasts(QUEUE_PREDICATE_ANY, (testRecord, testReceiver) -> {
+ // We only allow caller to clear broadcasts they enqueued
+ return (testRecord.callingUid == r.callingUid)
+ && removeMatching.test(testRecord.intent);
+ });
+ }
+
r.enqueueTime = SystemClock.uptimeMillis();
r.enqueueRealTime = SystemClock.elapsedRealtime();
r.enqueueClockTime = System.currentTimeMillis();
@@ -399,11 +500,20 @@
}
}
+ /**
+ * Schedule the currently active broadcast on the given queue when we know
+ * the process is cold. This kicks off a cold start and will eventually call
+ * through to {@link #scheduleReceiverWarmLocked} once it's ready.
+ */
private void scheduleReceiverColdLocked(@NonNull BroadcastProcessQueue queue) {
checkState(queue.isActive(), "isActive");
+ // Remember that active broadcast was scheduled via a cold start
+ queue.setActiveViaColdStart(true);
+
final BroadcastRecord r = queue.getActive();
- final Object receiver = queue.getActiveReceiver();
+ final int index = queue.getActiveIndex();
+ final Object receiver = r.receivers.get(index);
final ApplicationInfo info = ((ResolveInfo) receiver).activityInfo.applicationInfo;
final ComponentName component = ((ResolveInfo) receiver).activityInfo.getComponentName();
@@ -421,18 +531,52 @@
if (DEBUG_BROADCAST) logv("Scheduling " + r + " to cold " + queue);
queue.app = mService.startProcessLocked(queue.processName, info, true, intentFlags,
hostingRecord, zygotePolicyFlags, allowWhileBooting, false);
- if (queue.app == null) {
+ if (queue.app != null) {
+ notifyStartedRunning(queue);
+ } else {
mRunningColdStart = null;
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
}
}
+ /**
+ * Schedule the currently active broadcast on the given queue when we know
+ * the process is warm.
+ * <p>
+ * There is a <em>very strong</em> preference to consistently handle all
+ * results by calling through to {@link #finishReceiverLocked}, both in the
+ * case where a broadcast is handled by a remote app, and the case where the
+ * broadcast was finished locally without the remote app being involved.
+ */
private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) {
checkState(queue.isActive(), "isActive");
final ProcessRecord app = queue.app;
final BroadcastRecord r = queue.getActive();
- final Object receiver = queue.getActiveReceiver();
+ final int index = queue.getActiveIndex();
+ final Object receiver = r.receivers.get(index);
+
+ // If someone already finished this broadcast, finish immediately
+ final int oldDeliveryState = getDeliveryState(r, index);
+ if (isDeliveryStateTerminal(oldDeliveryState)) {
+ finishReceiverLocked(queue, oldDeliveryState);
+ return;
+ }
+
+ // Consider additional cases where we'd want fo finish immediately
+ if (app.isInFullBackup()) {
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+ return;
+ }
+ if (mSkipPolicy.shouldSkip(r, receiver)) {
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+ return;
+ }
+ final Intent receiverIntent = r.getReceiverIntent(receiver);
+ if (receiverIntent == null) {
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+ return;
+ }
if (!r.timeoutExempt) {
final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
@@ -440,26 +584,33 @@
Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT, queue), timeout);
}
- // TODO: apply temp allowlist exemptions
- // TODO: apply background activity launch exemptions
+ if (r.allowBackgroundActivityStarts) {
+ app.addOrUpdateAllowBackgroundActivityStartsToken(r, r.mBackgroundActivityStartsToken);
- if (mSkipPolicy.shouldSkip(r, receiver)) {
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
- return;
+ final long timeout = r.isForeground() ? mFgConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT
+ : mBgConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT;
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = app;
+ args.arg2 = r;
+ mLocalHandler.sendMessageDelayed(
+ Message.obtain(mLocalHandler, MSG_BG_ACTIVITY_START_TIMEOUT, args), timeout);
}
- final Intent receiverIntent = r.getReceiverIntent(receiver);
- if (receiverIntent == null) {
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
- return;
+ if (r.options != null && r.options.getTemporaryAppAllowlistDuration() > 0) {
+ mService.tempAllowlistUidLocked(queue.uid,
+ r.options.getTemporaryAppAllowlistDuration(),
+ r.options.getTemporaryAppAllowlistReasonCode(), r.toShortString(),
+ r.options.getTemporaryAppAllowlistType(), r.callingUid);
}
if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
+ setDeliveryState(queue, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED);
+
final IApplicationThread thread = app.getThread();
if (thread != null) {
try {
- queue.setActiveDeliveryState(BroadcastRecord.DELIVERY_SCHEDULED);
if (receiver instanceof BroadcastFilter) {
+ notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver);
thread.scheduleRegisteredReceiver(
((BroadcastFilter) receiver).receiverList.receiver, receiverIntent,
r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky,
@@ -471,26 +622,62 @@
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
}
} else {
+ notifyScheduleReceiver(app, r, (ResolveInfo) receiver);
thread.scheduleReceiver(receiverIntent, ((ResolveInfo) receiver).activityInfo,
null, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
app.mState.getReportedProcState());
}
} catch (RemoteException e) {
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
- synchronized (app.mService) {
- app.scheduleCrashLocked(TAG, CannotDeliverBroadcastException.TYPE_ID, null);
- }
+ app.scheduleCrashLocked(TAG, CannotDeliverBroadcastException.TYPE_ID, null);
}
} else {
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
}
}
+ /**
+ * Schedule the final {@link BroadcastRecord#resultTo} delivery for an
+ * ordered broadcast; assumes the sender is still a warm process.
+ */
+ private void scheduleResultTo(@NonNull BroadcastRecord r) {
+ if ((r.callerApp == null) || (r.resultTo == null)) return;
+ final ProcessRecord app = r.callerApp;
+ final IApplicationThread thread = app.getThread();
+ if (thread != null) {
+ mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
+ app, OOM_ADJ_REASON_FINISH_RECEIVER);
+ try {
+ thread.scheduleRegisteredReceiver(r.resultTo, r.intent,
+ r.resultCode, r.resultData, r.resultExtras, false, r.initialSticky,
+ r.userId, app.mState.getReportedProcState());
+ } catch (RemoteException e) {
+ app.scheduleCrashLocked(TAG, CannotDeliverBroadcastException.TYPE_ID, null);
+ }
+ }
+ }
+
@Override
public boolean finishReceiverLocked(@NonNull ProcessRecord app, int resultCode,
@Nullable String resultData, @Nullable Bundle resultExtras, boolean resultAbort,
boolean waitForServices) {
final BroadcastProcessQueue queue = getProcessQueue(app);
+ final BroadcastRecord r = queue.getActive();
+ r.resultCode = resultCode;
+ r.resultData = resultData;
+ r.resultExtras = resultExtras;
+ if (!r.isNoAbort()) {
+ r.resultAbort = resultAbort;
+ }
+
+ // When the caller aborted an ordered broadcast, we mark all remaining
+ // receivers as skipped
+ if (r.ordered && r.resultAbort) {
+ for (int i = r.finishedCount + 1; i < r.receivers.size(); i++) {
+ setDeliveryState(null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
+ }
+ }
+
return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
}
@@ -498,28 +685,26 @@
@DeliveryState int deliveryState) {
checkState(queue.isActive(), "isActive");
- queue.setActiveDeliveryState(deliveryState);
+ final ProcessRecord app = queue.app;
+ final BroadcastRecord r = queue.getActive();
+ final int index = queue.getActiveIndex();
+ final Object receiver = r.receivers.get(index);
- if (deliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
- Slog.w(TAG, "Delivery state of " + queue.getActive() + " to " + queue + " changed to "
- + BroadcastRecord.deliveryStateToString(deliveryState));
- }
+ setDeliveryState(queue, r, index, receiver, deliveryState);
if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
- if (queue.app != null && !queue.app.isDebugging()) {
+ if (app != null && !app.isDebugging()) {
mService.appNotResponding(queue.app, TimeoutRecord
- .forBroadcastReceiver("Broadcast of " + queue.getActive().toShortString()));
+ .forBroadcastReceiver("Broadcast of " + r.toShortString()));
}
} else {
mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT, queue);
}
- // TODO: if we're the last receiver of this broadcast, record to history
-
// Even if we have more broadcasts, if we've made reasonable progress
// and someone else is waiting, retire ourselves to avoid starvation
final boolean shouldRetire = (mRunnableHead != null)
- && (queue.getActiveCountSinceIdle() > MAX_RUNNING_ACTIVE_BROADCASTS);
+ && (queue.getActiveCountSinceIdle() > mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
if (queue.isRunnable() && queue.isProcessWarm() && !shouldRetire) {
// We're on a roll; move onto the next broadcast for this process
@@ -529,21 +714,150 @@
} else {
// We've drained running broadcasts; maybe move back to runnable
queue.makeActiveIdle();
- mRunning.remove(queue);
- // App is no longer running a broadcast, so update its OOM
- // adjust during our next pass; no need for an immediate update
- mService.enqueueOomAdjTargetLocked(queue.app);
+ queue.traceProcessEnd();
+
+ final int queueIndex = getRunningIndexOf(queue);
+ mRunning[queueIndex] = null;
updateRunnableList(queue);
enqueueUpdateRunningList();
+
+ // Tell other OS components that app is not actively running, giving
+ // a chance to update OOM adjustment
+ notifyStoppedRunning(queue);
return false;
}
}
+ /**
+ * Set the delivery state on the given broadcast, then apply any additional
+ * bookkeeping related to ordered broadcasts.
+ */
+ private void setDeliveryState(@Nullable BroadcastProcessQueue queue,
+ @NonNull BroadcastRecord r, int index, @NonNull Object receiver,
+ @DeliveryState int newDeliveryState) {
+ final int oldDeliveryState = getDeliveryState(r, index);
+
+ if (newDeliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
+ Slog.w(TAG, "Delivery state of " + r + " to " + receiver + " changed from "
+ + deliveryStateToString(oldDeliveryState) + " to "
+ + deliveryStateToString(newDeliveryState));
+ }
+
+ r.setDeliveryState(index, newDeliveryState);
+
+ // Emit any relevant tracing results when we're changing the delivery
+ // state as part of running from a queue
+ if (queue != null) {
+ if (newDeliveryState == BroadcastRecord.DELIVERY_SCHEDULED) {
+ queue.traceActiveBegin();
+ } else if ((oldDeliveryState == BroadcastRecord.DELIVERY_SCHEDULED)
+ && isDeliveryStateTerminal(newDeliveryState)) {
+ queue.traceActiveEnd();
+ }
+ }
+
+ // If we're moving into a terminal state, we might have internal
+ // bookkeeping to update for ordered broadcasts
+ if (!isDeliveryStateTerminal(oldDeliveryState)
+ && isDeliveryStateTerminal(newDeliveryState)) {
+ r.finishedCount++;
+ notifyFinishReceiver(queue, r, index, receiver);
+
+ if (r.ordered) {
+ if (r.finishedCount < r.receivers.size()) {
+ // We just finished an ordered receiver, which means the
+ // next receiver might now be runnable
+ final Object nextReceiver = r.receivers.get(r.finishedCount);
+ final BroadcastProcessQueue nextQueue = getProcessQueue(
+ getReceiverProcessName(nextReceiver), getReceiverUid(nextReceiver));
+ nextQueue.invalidateRunnableAt();
+ updateRunnableList(nextQueue);
+ } else {
+ // Everything finished, so deliver final result
+ scheduleResultTo(r);
+ }
+ }
+ }
+ }
+
+ private @DeliveryState int getDeliveryState(@NonNull BroadcastRecord r, int index) {
+ return r.getDeliveryState(index);
+ }
+
@Override
- public boolean cleanupDisabledPackageReceiversLocked(String packageName,
- Set<String> filterByClasses, int userId, boolean doit) {
- // TODO: implement
- return false;
+ public boolean cleanupDisabledPackageReceiversLocked(@Nullable String packageName,
+ @Nullable Set<String> filterByClasses, int userId) {
+ final Predicate<BroadcastProcessQueue> queuePredicate;
+ final BroadcastPredicate broadcastPredicate;
+ if (packageName != null) {
+ // Caller provided a package and user ID, so we're focused on queues
+ // belonging to a specific UID
+ final int uid = mService.mPackageManagerInt.getPackageUid(
+ packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
+ queuePredicate = (q) -> {
+ return q.uid == uid;
+ };
+
+ // If caller provided a set of classes, filter to skip only those;
+ // otherwise we skip all broadcasts
+ if (filterByClasses != null) {
+ broadcastPredicate = (r, i) -> {
+ final Object receiver = r.receivers.get(i);
+ if (receiver instanceof ResolveInfo) {
+ final ActivityInfo info = ((ResolveInfo) receiver).activityInfo;
+ return packageName.equals(info.packageName)
+ && filterByClasses.contains(info.name);
+ } else {
+ return false;
+ }
+ };
+ } else {
+ broadcastPredicate = (r, i) -> {
+ final Object receiver = r.receivers.get(i);
+ return packageName.equals(getReceiverPackageName(receiver));
+ };
+ }
+ } else {
+ // Caller is cleaning up an entire user ID; skip all broadcasts
+ queuePredicate = (q) -> {
+ return UserHandle.getUserId(q.uid) == userId;
+ };
+ broadcastPredicate = BROADCAST_PREDICATE_ANY;
+ }
+ return skipMatchingBroadcasts(queuePredicate, broadcastPredicate);
+ }
+
+ private static final Predicate<BroadcastProcessQueue> QUEUE_PREDICATE_ANY =
+ (q) -> true;
+ private static final BroadcastPredicate BROADCAST_PREDICATE_ANY =
+ (r, i) -> true;
+
+ /**
+ * Typical consumer that will skip the given broadcast, usually as a result
+ * of it matching a predicate.
+ */
+ private final BroadcastConsumer mBroadcastConsumerSkip = (r, i) -> {
+ setDeliveryState(null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
+ };
+
+ private boolean skipMatchingBroadcasts(
+ @NonNull Predicate<BroadcastProcessQueue> queuePredicate,
+ @NonNull BroadcastPredicate broadcastPredicate) {
+ // Note that we carefully preserve any "skipped" broadcasts in their
+ // queues so that we follow our normal flow for "finishing" a broadcast,
+ // which is where we handle things like ordered broadcasts.
+ boolean didSomething = false;
+ for (int i = 0; i < mProcessQueues.size(); i++) {
+ BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
+ while (leaf != null) {
+ if (queuePredicate.test(leaf)) {
+ didSomething |= leaf.removeMatchingBroadcasts(broadcastPredicate,
+ mBroadcastConsumerSkip);
+ }
+ leaf = leaf.processNameNext;
+ }
+ }
+ return didSomething;
}
@Override
@@ -569,7 +883,7 @@
@Override
public boolean isIdleLocked() {
- return (mRunnableHead == null) && mRunning.isEmpty();
+ return (mRunnableHead == null) && (getRunningSize() == 0);
}
@Override
@@ -594,7 +908,7 @@
@Override
public String describeStateLocked() {
- return mRunning.size() + " running";
+ return getRunningSize() + " running";
}
@Override
@@ -608,17 +922,179 @@
// TODO: implement
}
+ private int traceBegin(String trackName, String methodName) {
+ final int cookie = methodName.hashCode();
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ trackName, methodName, cookie);
+ return cookie;
+ }
+
+ private void traceEnd(String trackName, int cookie) {
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ trackName, cookie);
+ }
+
private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) {
if (!queue.isProcessWarm()) {
queue.app = mService.getProcessRecordLocked(queue.processName, queue.uid);
}
}
- private @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull ProcessRecord app) {
+ /**
+ * Inform other parts of OS that the given broadcast queue has started
+ * running, typically for internal bookkeeping.
+ */
+ private void notifyStartedRunning(@NonNull BroadcastProcessQueue queue) {
+ if (queue.app != null) {
+ queue.app.mReceivers.incrementCurReceivers();
+
+ queue.app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
+
+ // Don't bump its LRU position if it's in the background restricted.
+ if (mService.mInternal.getRestrictionLevel(
+ queue.uid) < ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
+ mService.updateLruProcessLocked(queue.app, false, null);
+ }
+
+ mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(queue.app,
+ OOM_ADJ_REASON_START_RECEIVER);
+
+ mService.enqueueOomAdjTargetLocked(queue.app);
+ }
+ }
+
+ /**
+ * Inform other parts of OS that the given broadcast queue has stopped
+ * running, typically for internal bookkeeping.
+ */
+ private void notifyStoppedRunning(@NonNull BroadcastProcessQueue queue) {
+ if (queue.app != null) {
+ // Update during our next pass; no need for an immediate update
+ mService.enqueueOomAdjTargetLocked(queue.app);
+
+ queue.app.mReceivers.decrementCurReceivers();
+ }
+ }
+
+ /**
+ * Inform other parts of OS that the given broadcast was just scheduled for
+ * a registered receiver, typically for internal bookkeeping.
+ */
+ private void notifyScheduleRegisteredReceiver(@NonNull ProcessRecord app,
+ @NonNull BroadcastRecord r, @NonNull BroadcastFilter receiver) {
+ reportUsageStatsBroadcastDispatched(app, r);
+ }
+
+ /**
+ * Inform other parts of OS that the given broadcast was just scheduled for
+ * a manifest receiver, typically for internal bookkeeping.
+ */
+ private void notifyScheduleReceiver(@NonNull ProcessRecord app,
+ @NonNull BroadcastRecord r, @NonNull ResolveInfo receiver) {
+ reportUsageStatsBroadcastDispatched(app, r);
+
+ final String receiverPackageName = receiver.activityInfo.packageName;
+ app.addPackage(receiverPackageName,
+ receiver.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
+
+ final boolean targetedBroadcast = r.intent.getComponent() != null;
+ final boolean targetedSelf = Objects.equals(r.callerPackage, receiverPackageName);
+ if (targetedBroadcast && !targetedSelf) {
+ mService.mUsageStatsService.reportEvent(receiverPackageName,
+ r.userId, Event.APP_COMPONENT_USED);
+ }
+
+ mService.notifyPackageUse(receiverPackageName,
+ PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
+
+ mService.mPackageManagerInt.setPackageStoppedState(
+ receiverPackageName, false, r.userId);
+ }
+
+ private void reportUsageStatsBroadcastDispatched(@NonNull ProcessRecord app,
+ @NonNull BroadcastRecord r) {
+ final long idForResponseEvent = (r.options != null)
+ ? r.options.getIdForResponseEvent() : 0L;
+ if (idForResponseEvent <= 0) return;
+
+ final String targetPackage;
+ if (r.intent.getPackage() != null) {
+ targetPackage = r.intent.getPackage();
+ } else if (r.intent.getComponent() != null) {
+ targetPackage = r.intent.getComponent().getPackageName();
+ } else {
+ targetPackage = null;
+ }
+ if (targetPackage == null) return;
+
+ mService.mUsageStatsService.reportBroadcastDispatched(r.callingUid, targetPackage,
+ UserHandle.of(r.userId), idForResponseEvent, SystemClock.elapsedRealtime(),
+ mService.getUidStateLocked(app.uid));
+ }
+
+ /**
+ * Inform other parts of OS that the given broadcast was just finished,
+ * typically for internal bookkeeping.
+ */
+ private void notifyFinishReceiver(@Nullable BroadcastProcessQueue queue,
+ @NonNull BroadcastRecord r, int index, @NonNull Object receiver) {
+ // Report statistics for each individual receiver
+ final int uid = getReceiverUid(receiver);
+ final int senderUid = (r.callingUid == -1) ? Process.SYSTEM_UID : r.callingUid;
+ final String actionName = ActivityManagerService.getShortAction(r.intent.getAction());
+ final int receiverType = (receiver instanceof BroadcastFilter)
+ ? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
+ : BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
+ final int type;
+ if (queue == null) {
+ type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_UNKNOWN;
+ } else if (queue.getActiveViaColdStart()) {
+ type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
+ } else {
+ type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
+ }
+ // With the new per-process queues, there's no delay between being
+ // "dispatched" and "scheduled", so we report no "receive delay"
+ final long dispatchDelay = r.scheduledTime[index] - r.enqueueTime;
+ final long receiveDelay = 0;
+ final long finishDelay = r.duration[index];
+ FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, uid, senderUid, actionName,
+ receiverType, type, dispatchDelay, receiveDelay, finishDelay);
+
+ final boolean recordFinished = (r.finishedCount == r.receivers.size());
+ if (recordFinished) {
+ mHistory.addBroadcastToHistoryLocked(r);
+
+ r.nextReceiver = r.receivers.size();
+ BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
+
+ if (r.intent.getComponent() == null && r.intent.getPackage() == null
+ && (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+ int manifestCount = 0;
+ int manifestSkipCount = 0;
+ for (int i = 0; i < r.receivers.size(); i++) {
+ if (r.receivers.get(i) instanceof ResolveInfo) {
+ manifestCount++;
+ if (r.delivery[i] == BroadcastRecord.DELIVERY_SKIPPED) {
+ manifestSkipCount++;
+ }
+ }
+ }
+
+ final long dispatchTime = SystemClock.uptimeMillis() - r.enqueueTime;
+ mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
+ manifestCount, manifestSkipCount, dispatchTime);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull ProcessRecord app) {
return getOrCreateProcessQueue(app.processName, app.info.uid);
}
- private @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull String processName,
+ @VisibleForTesting
+ @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull String processName,
int uid) {
BroadcastProcessQueue leaf = mProcessQueues.get(uid);
while (leaf != null) {
@@ -630,7 +1106,7 @@
leaf = leaf.processNameNext;
}
- BroadcastProcessQueue created = new BroadcastProcessQueue(processName, uid);
+ BroadcastProcessQueue created = new BroadcastProcessQueue(mConstants, processName, uid);
created.app = mService.getProcessRecordLocked(processName, uid);
if (leaf == null) {
@@ -641,11 +1117,13 @@
return created;
}
- private @Nullable BroadcastProcessQueue getProcessQueue(@NonNull ProcessRecord app) {
+ @VisibleForTesting
+ @Nullable BroadcastProcessQueue getProcessQueue(@NonNull ProcessRecord app) {
return getProcessQueue(app.processName, app.info.uid);
}
- private @Nullable BroadcastProcessQueue getProcessQueue(@NonNull String processName, int uid) {
+ @VisibleForTesting
+ @Nullable BroadcastProcessQueue getProcessQueue(@NonNull String processName, int uid) {
BroadcastProcessQueue leaf = mProcessQueues.get(uid);
while (leaf != null) {
if (Objects.equals(leaf.processName, processName)) {
@@ -656,6 +1134,35 @@
return null;
}
+ @VisibleForTesting
+ @Nullable BroadcastProcessQueue removeProcessQueue(@NonNull ProcessRecord app) {
+ return removeProcessQueue(app.processName, app.info.uid);
+ }
+
+ @VisibleForTesting
+ @Nullable BroadcastProcessQueue removeProcessQueue(@NonNull String processName,
+ int uid) {
+ BroadcastProcessQueue prev = null;
+ BroadcastProcessQueue leaf = mProcessQueues.get(uid);
+ while (leaf != null) {
+ if (Objects.equals(leaf.processName, processName)) {
+ if (prev != null) {
+ prev.processNameNext = leaf.processNameNext;
+ } else {
+ if (leaf.processNameNext != null) {
+ mProcessQueues.put(uid, leaf.processNameNext);
+ } else {
+ mProcessQueues.remove(uid);
+ }
+ }
+ return leaf;
+ }
+ prev = leaf;
+ leaf = leaf.processNameNext;
+ }
+ return null;
+ }
+
@Override
public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
@@ -703,16 +1210,16 @@
ipw.println();
ipw.println("🏃 Running:");
ipw.increaseIndent();
- if (mRunning.isEmpty()) {
- ipw.println("(none)");
- } else {
- for (BroadcastProcessQueue queue : mRunning) {
- if (queue == mRunningColdStart) {
- ipw.print("🥶 ");
- } else {
- ipw.print("\u3000 ");
- }
+ for (BroadcastProcessQueue queue : mRunning) {
+ if ((queue != null) && (queue == mRunningColdStart)) {
+ ipw.print("🥶 ");
+ } else {
+ ipw.print("\u3000 ");
+ }
+ if (queue != null) {
ipw.println(queue.toShortString());
+ } else {
+ ipw.println("(none)");
}
}
ipw.decreaseIndent();
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 16eeb7bf..ae7f2a5 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -23,7 +23,9 @@
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_CHANGE_ID;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY;
+import static com.android.server.am.BroadcastQueue.checkState;
+import android.annotation.DurationMillisLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -111,6 +113,7 @@
int anrCount; // has this broadcast record hit any ANRs?
int manifestCount; // number of manifest receivers dispatched.
int manifestSkipCount; // number of manifest receivers skipped.
+ int finishedCount; // number of receivers finished.
BroadcastQueue queue; // the outbound queue handling this broadcast
// if set to true, app's process will be temporarily allowed to start activities from background
@@ -167,6 +170,22 @@
}
}
+ /**
+ * Return if the given delivery state is "terminal", where no additional
+ * delivery state changes will be made.
+ */
+ static boolean isDeliveryStateTerminal(@DeliveryState int deliveryState) {
+ switch (deliveryState) {
+ case DELIVERY_DELIVERED:
+ case DELIVERY_SKIPPED:
+ case DELIVERY_TIMEOUT:
+ case DELIVERY_FAILURE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
ProcessRecord curApp; // hosting application of current receiver.
ComponentName curComponent; // the receiver class that is currently running.
ActivityInfo curReceiver; // the manifest receiver that is currently running.
@@ -545,6 +564,10 @@
}
}
+ @DeliveryState int getDeliveryState(int index) {
+ return delivery[index];
+ }
+
boolean isForeground() {
return (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
}
@@ -553,6 +576,10 @@
return (intent.getFlags() & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
}
+ boolean isNoAbort() {
+ return (intent.getFlags() & Intent.FLAG_RECEIVER_NO_ABORT) != 0;
+ }
+
@NonNull String getHostingRecordTriggerType() {
if (alarm) {
return HostingRecord.TRIGGER_TYPE_ALARM;
@@ -606,7 +633,7 @@
}
}
- static String getReceiverProcessName(@NonNull Object receiver) {
+ static @NonNull String getReceiverProcessName(@NonNull Object receiver) {
if (receiver instanceof BroadcastFilter) {
return ((BroadcastFilter) receiver).receiverList.app.processName;
} else /* if (receiver instanceof ResolveInfo) */ {
@@ -614,6 +641,14 @@
}
}
+ static @NonNull String getReceiverPackageName(@NonNull Object receiver) {
+ if (receiver instanceof BroadcastFilter) {
+ return ((BroadcastFilter) receiver).receiverList.app.info.packageName;
+ } else /* if (receiver instanceof ResolveInfo) */ {
+ return ((ResolveInfo) receiver).activityInfo.packageName;
+ }
+ }
+
public BroadcastRecord maybeStripForHistory() {
if (!intent.canStripForHistory()) {
return this;
@@ -665,13 +700,21 @@
@Override
public String toString() {
+ String label = intent.getAction();
+ if (label == null) {
+ label = intent.toString();
+ }
return "BroadcastRecord{"
+ Integer.toHexString(System.identityHashCode(this))
- + " u" + userId + " " + intent.getAction() + "}";
+ + " u" + userId + " " + label + "}";
}
public String toShortString() {
- return intent.getAction() + "/u" + userId;
+ String label = intent.getAction();
+ if (label == null) {
+ label = intent.toString();
+ }
+ return label + "/u" + userId;
}
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/services/core/java/com/android/server/am/ProcessReceiverRecord.java b/services/core/java/com/android/server/am/ProcessReceiverRecord.java
index 8d3e9669..34a2b03 100644
--- a/services/core/java/com/android/server/am/ProcessReceiverRecord.java
+++ b/services/core/java/com/android/server/am/ProcessReceiverRecord.java
@@ -34,29 +34,61 @@
*/
private final ArraySet<BroadcastRecord> mCurReceivers = new ArraySet<BroadcastRecord>();
+ private int mCurReceiversSize;
+
/**
* All IIntentReceivers that are registered from this process.
*/
private final ArraySet<ReceiverList> mReceivers = new ArraySet<>();
int numberOfCurReceivers() {
- return mCurReceivers.size();
+ return mCurReceiversSize;
}
+ void incrementCurReceivers() {
+ mCurReceiversSize++;
+ }
+
+ void decrementCurReceivers() {
+ mCurReceiversSize--;
+ }
+
+ /**
+ * @deprecated we're moving towards tracking only a reference count to
+ * improve performance.
+ */
+ @Deprecated
BroadcastRecord getCurReceiverAt(int index) {
return mCurReceivers.valueAt(index);
}
+ /**
+ * @deprecated we're moving towards tracking only a reference count to
+ * improve performance.
+ */
+ @Deprecated
boolean hasCurReceiver(BroadcastRecord receiver) {
return mCurReceivers.contains(receiver);
}
+ /**
+ * @deprecated we're moving towards tracking only a reference count to
+ * improve performance.
+ */
+ @Deprecated
void addCurReceiver(BroadcastRecord receiver) {
mCurReceivers.add(receiver);
+ mCurReceiversSize = mCurReceivers.size();
}
+ /**
+ * @deprecated we're moving towards tracking only a reference count to
+ * improve performance.
+ */
+ @Deprecated
void removeCurReceiver(BroadcastRecord receiver) {
mCurReceivers.remove(receiver);
+ mCurReceiversSize = mCurReceivers.size();
}
int numberOfReceivers() {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 7c7b01c9..82fb1e8 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1387,7 +1387,7 @@
int i = 0;
for (; i < profilesToStartSize && i < (getMaxRunningUsers() - 1); ++i) {
// NOTE: this method is setting the profiles of the current user - which is always
- // assigned to the default display - so there's no need to pass PARENT_DISPLAY
+ // assigned to the default display
startUser(profilesToStart.get(i).id, /* foreground= */ false);
}
if (i < profilesToStartSize) {
@@ -1430,10 +1430,7 @@
return false;
}
- int displayId = mInjector.isUsersOnSecondaryDisplaysEnabled()
- ? UserManagerInternal.PARENT_DISPLAY
- : Display.DEFAULT_DISPLAY;
- return startUserNoChecks(userId, displayId, /* foreground= */ false,
+ return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, /* foreground= */ false,
/* unlockListener= */ null);
}
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictions.java b/services/core/java/com/android/server/appop/AppOpsRestrictions.java
new file mode 100644
index 0000000..f7ccd34
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictions.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import android.os.PackageTagsList;
+
+import java.io.PrintWriter;
+
+/**
+ * Legacy implementation for AppOpsService's app-op restrictions (global and user)
+ * storage and access.
+ */
+public interface AppOpsRestrictions {
+ /**
+ * Set or clear a global app-op restriction for the given {@code clientToken}.
+ *
+ * @param clientToken A token identifying the client this restriction applies to.
+ * @param code The app-op opCode to set (or clear) a restriction for.
+ * @param restricted {@code true} to restrict this app-op code, or {@code false} to clear an
+ * existing restriction.
+ * @return {@code true} if any restriction state was modified as a result of this operation
+ */
+ boolean setGlobalRestriction(Object clientToken, int code, boolean restricted);
+
+ /**
+ * Get the state of a global app-op restriction for the given {@code clientToken}.
+ *
+ * @param clientToken A token identifying the client to get the restriction state of.
+ * @param code The app-op code to get the restriction state of.
+ * @return the restriction state
+ */
+ boolean getGlobalRestriction(Object clientToken, int code);
+
+ /**
+ * Returns {@code true} if *any* global app-op restrictions are currently set for the given
+ * {@code clientToken}.
+ *
+ * @param clientToken A token identifying the client to check restrictions for.
+ * @return {@code true} if any restrictions are set
+ */
+ boolean hasGlobalRestrictions(Object clientToken);
+
+ /**
+ * Clear *all* global app-op restrictions for the given {@code clientToken}.
+ *
+ * @param clientToken A token identifying the client to clear restrictions from.
+ * @return {@code true} if any restriction state was modified as a result of this operation
+ */
+ boolean clearGlobalRestrictions(Object clientToken);
+
+ /**
+ * Set or clear a user app-op restriction for the given {@code clientToken} and {@code userId}.
+ *
+ * @param clientToken A token identifying the client this restriction applies to.
+ * @param code The app-op code to set (or clear) a restriction for.
+ * @param restricted {@code true} to restrict this app-op code, or {@code false} to
+ * remove any existing restriction.
+ * @param excludedPackageTags A list of packages and associated attribution tags to exclude
+ * from this restriction. Or, if {@code null}, removes any
+ * exclusions from this restriction.
+ * @return {@code true} if any restriction state was modified as a result of this operation
+ */
+ boolean setUserRestriction(Object clientToken, int userId, int code, boolean restricted,
+ PackageTagsList excludedPackageTags);
+
+ /**
+ * Get the state of a user app-op restriction for the given {@code clientToken} and {@code
+ * userId}. Or, if the combination of ({{@code clientToken}, {@code userId}, @code
+ * packageName}, {@code attributionTag}) has been excluded via
+ * {@link AppOpsRestrictions#setUserRestriction}, always returns {@code false}.
+ *
+ * @param clientToken A token identifying the client this restriction applies to.
+ * @param userId Which userId this restriction applies to.
+ * @param code The app-op code to get the restriction state of.
+ * @param packageName A package name used to check for exclusions.
+ * @param attributionTag An attribution tag used to check for exclusions.
+ * @param isCheckOp a flag that, when {@code true}, denotes that exclusions should be
+ * checked by (packageName) rather than (packageName, attributionTag)
+ * @return the restriction state
+ */
+ boolean getUserRestriction(Object clientToken, int userId, int code, String packageName,
+ String attributionTag, boolean isCheckOp);
+
+ /**
+ * Returns {@code true} if *any* user app-op restrictions are currently set for the given
+ * {@code clientToken}.
+ *
+ * @param clientToken A token identifying the client to check restrictions for.
+ * @return {@code true} if any restrictions are set
+ */
+ boolean hasUserRestrictions(Object clientToken);
+
+ /**
+ * Clear *all* user app-op restrictions for the given {@code clientToken}.
+ *
+ * @param clientToken A token identifying the client to clear restrictions for.
+ * @return {@code true} if any restriction state was modified as a result of this operation
+ */
+ boolean clearUserRestrictions(Object clientToken);
+
+ /**
+ * Clear *all* user app-op restrictions for the given {@code clientToken} and {@code userId}.
+ *
+ * @param clientToken A token identifying the client to clear restrictions for.
+ * @param userId Which userId to clear restrictions for.
+ * @return {@code true} if any restriction state was modified as a result of this operation
+ */
+ boolean clearUserRestrictions(Object clientToken, Integer userId);
+
+ /**
+ * Returns the set of exclusions previously set by
+ * {@link AppOpsRestrictions#setUserRestriction} for the given {@code clientToken}
+ * and {@code userId}.
+ *
+ * @param clientToken A token identifying the client to get restriction exclusions for.
+ * @param userId Which userId to get restriction exclusions for
+ * @return a set of user restriction exclusions
+ */
+ PackageTagsList getUserRestrictionExclusions(Object clientToken, int userId);
+
+ /**
+ * Dump the state of appop restrictions.
+ *
+ * @param printWriter writer to dump to.
+ * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's
+ * set to an app-op, only the watchers for that app-op are dumped.
+ * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package
+ * name.
+ * @param showUserRestrictions include user restriction state in the output
+ */
+ void dumpRestrictions(PrintWriter printWriter, int dumpOp, String dumpPackage,
+ boolean showUserRestrictions);
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
new file mode 100644
index 0000000..adfd2af
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import android.annotation.RequiresPermission;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.os.PackageTagsList;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Implementation for AppOpsService's app-op restrictions (global and user) storage and retrieval.
+ */
+public class AppOpsRestrictionsImpl implements AppOpsRestrictions {
+
+ private static final int UID_ANY = -2;
+
+ private Context mContext;
+ private Handler mHandler;
+ private AppOpsServiceInterface mAppOpsServiceInterface;
+
+ // Map from (Object token) to (int code) to (boolean restricted)
+ private final ArrayMap<Object, SparseBooleanArray> mGlobalRestrictions = new ArrayMap<>();
+
+ // Map from (Object token) to (int userId) to (int code) to (boolean restricted)
+ private final ArrayMap<Object, SparseArray<SparseBooleanArray>> mUserRestrictions =
+ new ArrayMap<>();
+
+ // Map from (Object token) to (int userId) to (PackageTagsList packageTagsList)
+ private final ArrayMap<Object, SparseArray<PackageTagsList>>
+ mUserRestrictionExcludedPackageTags = new ArrayMap<>();
+
+ public AppOpsRestrictionsImpl(Context context, Handler handler,
+ AppOpsServiceInterface appOpsServiceInterface) {
+ mContext = context;
+ mHandler = handler;
+ mAppOpsServiceInterface = appOpsServiceInterface;
+ }
+
+ @Override
+ public boolean setGlobalRestriction(Object clientToken, int code, boolean restricted) {
+ if (restricted) {
+ if (!mGlobalRestrictions.containsKey(clientToken)) {
+ mGlobalRestrictions.put(clientToken, new SparseBooleanArray());
+ }
+ SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
+ Objects.requireNonNull(restrictedCodes);
+ boolean changed = !restrictedCodes.get(code);
+ restrictedCodes.put(code, true);
+ return changed;
+ } else {
+ SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
+ if (restrictedCodes == null) {
+ return false;
+ }
+ boolean changed = restrictedCodes.get(code);
+ restrictedCodes.delete(code);
+ if (restrictedCodes.size() == 0) {
+ mGlobalRestrictions.remove(clientToken);
+ }
+ return changed;
+ }
+ }
+
+ @Override
+ public boolean getGlobalRestriction(Object clientToken, int code) {
+ SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
+ if (restrictedCodes == null) {
+ return false;
+ }
+ return restrictedCodes.get(code);
+ }
+
+ @Override
+ public boolean hasGlobalRestrictions(Object clientToken) {
+ return mGlobalRestrictions.containsKey(clientToken);
+ }
+
+ @Override
+ public boolean clearGlobalRestrictions(Object clientToken) {
+ return mGlobalRestrictions.remove(clientToken) != null;
+ }
+
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
+ @Override
+ public boolean setUserRestriction(Object clientToken, int userId, int code,
+ boolean restricted,
+ PackageTagsList excludedPackageTags) {
+ int[] userIds = resolveUserId(userId);
+ boolean changed = false;
+ for (int i = 0; i < userIds.length; i++) {
+ changed |= putUserRestriction(clientToken, userIds[i], code, restricted);
+ changed |= putUserRestrictionExclusions(clientToken, userIds[i],
+ excludedPackageTags);
+ }
+ return changed;
+ }
+
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
+ private int[] resolveUserId(int userId) {
+ int[] userIds;
+ if (userId == UserHandle.USER_ALL) {
+ // TODO(b/162888972): this call is returning all users, not just live ones - we
+ // need to either fix the method called, or rename the variable
+ List<UserInfo> liveUsers = UserManager.get(mContext).getUsers();
+
+ userIds = new int[liveUsers.size()];
+ for (int i = 0; i < liveUsers.size(); i++) {
+ userIds[i] = liveUsers.get(i).id;
+ }
+ } else {
+ userIds = new int[]{userId};
+ }
+ return userIds;
+ }
+
+ @Override
+ public boolean hasUserRestrictions(Object clientToken) {
+ return mUserRestrictions.containsKey(clientToken);
+ }
+
+ private boolean getUserRestriction(Object clientToken, int userId, int code) {
+ SparseArray<SparseBooleanArray> userIdRestrictedCodes =
+ mUserRestrictions.get(clientToken);
+ if (userIdRestrictedCodes == null) {
+ return false;
+ }
+ SparseBooleanArray restrictedCodes = userIdRestrictedCodes.get(userId);
+ if (restrictedCodes == null) {
+ return false;
+ }
+ return restrictedCodes.get(code);
+ }
+
+ @Override
+ public boolean getUserRestriction(Object clientToken, int userId, int code, String packageName,
+ String attributionTag, boolean isCheckOp) {
+ boolean restricted = getUserRestriction(clientToken, userId, code);
+ if (!restricted) {
+ return false;
+ }
+
+ PackageTagsList perUserExclusions = getUserRestrictionExclusions(clientToken, userId);
+ if (perUserExclusions == null) {
+ return true;
+ }
+
+ // TODO (b/240617242) add overload for checkOp to support attribution tags
+ if (isCheckOp) {
+ return !perUserExclusions.includes(packageName);
+ }
+ return !perUserExclusions.contains(packageName, attributionTag);
+ }
+
+ @Override
+ public boolean clearUserRestrictions(Object clientToken) {
+ boolean changed = false;
+ SparseBooleanArray allUserRestrictedCodes = collectAllUserRestrictedCodes(clientToken);
+ changed |= mUserRestrictions.remove(clientToken) != null;
+ changed |= mUserRestrictionExcludedPackageTags.remove(clientToken) != null;
+ notifyAllUserRestrictions(allUserRestrictedCodes);
+ return changed;
+ }
+
+ private SparseBooleanArray collectAllUserRestrictedCodes(Object clientToken) {
+ SparseBooleanArray allRestrictedCodes = new SparseBooleanArray();
+ SparseArray<SparseBooleanArray> userIdRestrictedCodes = mUserRestrictions.get(clientToken);
+ if (userIdRestrictedCodes == null) {
+ return allRestrictedCodes;
+ }
+ int userIdRestrictedCodesSize = userIdRestrictedCodes.size();
+ for (int i = 0; i < userIdRestrictedCodesSize; i++) {
+ SparseBooleanArray restrictedCodes = userIdRestrictedCodes.valueAt(i);
+ int restrictedCodesSize = restrictedCodes.size();
+ for (int j = 0; j < restrictedCodesSize; j++) {
+ int code = restrictedCodes.keyAt(j);
+ allRestrictedCodes.put(code, true);
+ }
+ }
+ return allRestrictedCodes;
+ }
+
+ // TODO: For clearUserRestrictions, we are calling notifyOpChanged from within the
+ // LegacyAppOpsServiceInterfaceImpl class. But, for all other changes to restrictions, we're
+ // calling it from within AppOpsService. This is awkward, and we should probably do it one
+ // way or the other.
+ private void notifyAllUserRestrictions(SparseBooleanArray allUserRestrictedCodes) {
+ int restrictedCodesSize = allUserRestrictedCodes.size();
+ for (int j = 0; j < restrictedCodesSize; j++) {
+ int code = allUserRestrictedCodes.keyAt(j);
+ mHandler.post(() -> mAppOpsServiceInterface.notifyWatchersOfChange(code, UID_ANY));
+ }
+ }
+
+ @Override
+ public boolean clearUserRestrictions(Object clientToken, Integer userId) {
+ boolean changed = false;
+
+ SparseArray<SparseBooleanArray> userIdRestrictedCodes =
+ mUserRestrictions.get(clientToken);
+ if (userIdRestrictedCodes != null) {
+ changed |= userIdRestrictedCodes.contains(userId);
+ userIdRestrictedCodes.remove(userId);
+ if (userIdRestrictedCodes.size() == 0) {
+ mUserRestrictions.remove(clientToken);
+ }
+ }
+
+ SparseArray<PackageTagsList> userIdPackageTags =
+ mUserRestrictionExcludedPackageTags.get(clientToken);
+ if (userIdPackageTags != null) {
+ changed |= userIdPackageTags.contains(userId);
+ userIdPackageTags.remove(userId);
+ if (userIdPackageTags.size() == 0) {
+ mUserRestrictionExcludedPackageTags.remove(clientToken);
+ }
+ }
+
+ return changed;
+ }
+
+ private boolean putUserRestriction(Object token, int userId, int code, boolean restricted) {
+ boolean changed = false;
+ if (restricted) {
+ if (!mUserRestrictions.containsKey(token)) {
+ mUserRestrictions.put(token, new SparseArray<>());
+ }
+ SparseArray<SparseBooleanArray> userIdRestrictedCodes = mUserRestrictions.get(token);
+ Objects.requireNonNull(userIdRestrictedCodes);
+
+ if (!userIdRestrictedCodes.contains(userId)) {
+ userIdRestrictedCodes.put(userId, new SparseBooleanArray());
+ }
+ SparseBooleanArray restrictedCodes = userIdRestrictedCodes.get(userId);
+
+ changed = !restrictedCodes.get(code);
+ restrictedCodes.put(code, restricted);
+ } else {
+ SparseArray<SparseBooleanArray> userIdRestrictedCodes = mUserRestrictions.get(token);
+ if (userIdRestrictedCodes == null) {
+ return false;
+ }
+ SparseBooleanArray restrictedCodes = userIdRestrictedCodes.get(userId);
+ if (restrictedCodes == null) {
+ return false;
+ }
+ changed = restrictedCodes.get(code);
+ restrictedCodes.delete(code);
+ if (restrictedCodes.size() == 0) {
+ userIdRestrictedCodes.remove(userId);
+ }
+ if (userIdRestrictedCodes.size() == 0) {
+ mUserRestrictions.remove(token);
+ }
+ }
+ return changed;
+ }
+
+ @Override
+ public PackageTagsList getUserRestrictionExclusions(Object clientToken, int userId) {
+ SparseArray<PackageTagsList> userIdPackageTags =
+ mUserRestrictionExcludedPackageTags.get(clientToken);
+ if (userIdPackageTags == null) {
+ return null;
+ }
+ return userIdPackageTags.get(userId);
+ }
+
+ private boolean putUserRestrictionExclusions(Object token, int userId,
+ PackageTagsList excludedPackageTags) {
+ boolean addingExclusions = excludedPackageTags != null && !excludedPackageTags.isEmpty();
+ if (addingExclusions) {
+ if (!mUserRestrictionExcludedPackageTags.containsKey(token)) {
+ mUserRestrictionExcludedPackageTags.put(token, new SparseArray<>());
+ }
+ SparseArray<PackageTagsList> userIdExcludedPackageTags =
+ mUserRestrictionExcludedPackageTags.get(token);
+ Objects.requireNonNull(userIdExcludedPackageTags);
+
+ userIdExcludedPackageTags.put(userId, excludedPackageTags);
+ return true;
+ } else {
+ SparseArray<PackageTagsList> userIdExclusions =
+ mUserRestrictionExcludedPackageTags.get(token);
+ if (userIdExclusions == null) {
+ return false;
+ }
+ boolean changed = userIdExclusions.get(userId) != null;
+ userIdExclusions.remove(userId);
+ if (userIdExclusions.size() == 0) {
+ mUserRestrictionExcludedPackageTags.remove(token);
+ }
+ return changed;
+ }
+ }
+
+ @Override
+ public void dumpRestrictions(PrintWriter pw, int code, String dumpPackage,
+ boolean showUserRestrictions) {
+ final int globalRestrictionCount = mGlobalRestrictions.size();
+ for (int i = 0; i < globalRestrictionCount; i++) {
+ Object token = mGlobalRestrictions.keyAt(i);
+ SparseBooleanArray restrictedOps = mGlobalRestrictions.valueAt(i);
+
+ pw.println(" Global restrictions for token " + token + ":");
+ StringBuilder restrictedOpsValue = new StringBuilder();
+ restrictedOpsValue.append("[");
+ final int restrictedOpCount = restrictedOps.size();
+ for (int j = 0; j < restrictedOpCount; j++) {
+ if (restrictedOpsValue.length() > 1) {
+ restrictedOpsValue.append(", ");
+ }
+ restrictedOpsValue.append(AppOpsManager.opToName(restrictedOps.keyAt(j)));
+ }
+ restrictedOpsValue.append("]");
+ pw.println(" Restricted ops: " + restrictedOpsValue);
+ }
+
+ if (!showUserRestrictions) {
+ return;
+ }
+
+ final int userRestrictionCount = mUserRestrictions.size();
+ for (int i = 0; i < userRestrictionCount; i++) {
+ Object token = mUserRestrictions.keyAt(i);
+ SparseArray<SparseBooleanArray> perUserRestrictions = mUserRestrictions.get(token);
+ SparseArray<PackageTagsList> perUserExcludedPackageTags =
+ mUserRestrictionExcludedPackageTags.get(token);
+
+ boolean printedTokenHeader = false;
+
+ final int restrictionCount = perUserRestrictions != null
+ ? perUserRestrictions.size() : 0;
+ if (restrictionCount > 0 && dumpPackage == null) {
+ boolean printedOpsHeader = false;
+ for (int j = 0; j < restrictionCount; j++) {
+ int userId = perUserRestrictions.keyAt(j);
+ SparseBooleanArray restrictedOps = perUserRestrictions.valueAt(j);
+ if (restrictedOps == null) {
+ continue;
+ }
+ if (code >= 0 && !restrictedOps.get(code)) {
+ continue;
+ }
+ if (!printedTokenHeader) {
+ pw.println(" User restrictions for token " + token + ":");
+ printedTokenHeader = true;
+ }
+ if (!printedOpsHeader) {
+ pw.println(" Restricted ops:");
+ printedOpsHeader = true;
+ }
+ StringBuilder restrictedOpsValue = new StringBuilder();
+ restrictedOpsValue.append("[");
+ final int restrictedOpCount = restrictedOps.size();
+ for (int k = 0; k < restrictedOpCount; k++) {
+ int restrictedOp = restrictedOps.keyAt(k);
+ if (restrictedOpsValue.length() > 1) {
+ restrictedOpsValue.append(", ");
+ }
+ restrictedOpsValue.append(AppOpsManager.opToName(restrictedOp));
+ }
+ restrictedOpsValue.append("]");
+ pw.print(" ");
+ pw.print("user: ");
+ pw.print(userId);
+ pw.print(" restricted ops: ");
+ pw.println(restrictedOpsValue);
+ }
+ }
+
+ final int excludedPackageCount = perUserExcludedPackageTags != null
+ ? perUserExcludedPackageTags.size() : 0;
+ if (excludedPackageCount > 0 && code < 0) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ ipw.increaseIndent();
+ boolean printedPackagesHeader = false;
+ for (int j = 0; j < excludedPackageCount; j++) {
+ int userId = perUserExcludedPackageTags.keyAt(j);
+ PackageTagsList packageNames =
+ perUserExcludedPackageTags.valueAt(j);
+ if (packageNames == null) {
+ continue;
+ }
+ boolean hasPackage;
+ if (dumpPackage != null) {
+ hasPackage = packageNames.includes(dumpPackage);
+ } else {
+ hasPackage = true;
+ }
+ if (!hasPackage) {
+ continue;
+ }
+ if (!printedTokenHeader) {
+ ipw.println("User restrictions for token " + token + ":");
+ printedTokenHeader = true;
+ }
+
+ ipw.increaseIndent();
+ if (!printedPackagesHeader) {
+ ipw.println("Excluded packages:");
+ printedPackagesHeader = true;
+ }
+
+ ipw.increaseIndent();
+ ipw.print("user: ");
+ ipw.print(userId);
+ ipw.println(" packages: ");
+
+ ipw.increaseIndent();
+ packageNames.dump(ipw);
+
+ ipw.decreaseIndent();
+ ipw.decreaseIndent();
+ ipw.decreaseIndent();
+ }
+ ipw.decreaseIndent();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index b844fc6..072d17f 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -97,7 +97,6 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PermissionInfo;
-import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
import android.net.Uri;
@@ -118,14 +117,12 @@
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.os.UserManager;
import android.os.storage.StorageManagerInternal;
import android.permission.PermissionManager;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
-import android.util.IndentingPrintWriter;
import android.util.KeyValueListParser;
import android.util.Pair;
import android.util.Slog;
@@ -367,6 +364,9 @@
/** Interface for app-op modes.*/
@VisibleForTesting AppOpsServiceInterface mAppOpsServiceInterface;
+ /** Interface for app-op restrictions.*/
+ @VisibleForTesting AppOpsRestrictions mAppOpsRestrictions;
+
private AppOpsUidStateTracker mUidStateTracker;
/** Hands the definition of foreground and uid states */
@@ -926,6 +926,8 @@
}
mAppOpsServiceInterface =
new LegacyAppOpsServiceInterfaceImpl(this, this, handler, context, mSwitchedOps);
+ mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
+ mAppOpsServiceInterface);
LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
mFile = new AtomicFile(storagePath, "appops");
@@ -5348,124 +5350,8 @@
pw.println();
}
- final int globalRestrictionCount = mOpGlobalRestrictions.size();
- for (int i = 0; i < globalRestrictionCount; i++) {
- IBinder token = mOpGlobalRestrictions.keyAt(i);
- ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
- ArraySet<Integer> restrictedOps = restrictionState.mRestrictedOps;
-
- pw.println(" Global restrictions for token " + token + ":");
- StringBuilder restrictedOpsValue = new StringBuilder();
- restrictedOpsValue.append("[");
- final int restrictedOpCount = restrictedOps.size();
- for (int j = 0; j < restrictedOpCount; j++) {
- if (restrictedOpsValue.length() > 1) {
- restrictedOpsValue.append(", ");
- }
- restrictedOpsValue.append(AppOpsManager.opToName(restrictedOps.valueAt(j)));
- }
- restrictedOpsValue.append("]");
- pw.println(" Restricted ops: " + restrictedOpsValue);
-
- }
-
- final int userRestrictionCount = mOpUserRestrictions.size();
- for (int i = 0; i < userRestrictionCount; i++) {
- IBinder token = mOpUserRestrictions.keyAt(i);
- ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
- boolean printedTokenHeader = false;
-
- if (dumpMode >= 0 || dumpWatchers || dumpHistory) {
- continue;
- }
-
- final int restrictionCount = restrictionState.perUserRestrictions != null
- ? restrictionState.perUserRestrictions.size() : 0;
- if (restrictionCount > 0 && dumpPackage == null) {
- boolean printedOpsHeader = false;
- for (int j = 0; j < restrictionCount; j++) {
- int userId = restrictionState.perUserRestrictions.keyAt(j);
- boolean[] restrictedOps = restrictionState.perUserRestrictions.valueAt(j);
- if (restrictedOps == null) {
- continue;
- }
- if (dumpOp >= 0 && (dumpOp >= restrictedOps.length
- || !restrictedOps[dumpOp])) {
- continue;
- }
- if (!printedTokenHeader) {
- pw.println(" User restrictions for token " + token + ":");
- printedTokenHeader = true;
- }
- if (!printedOpsHeader) {
- pw.println(" Restricted ops:");
- printedOpsHeader = true;
- }
- StringBuilder restrictedOpsValue = new StringBuilder();
- restrictedOpsValue.append("[");
- final int restrictedOpCount = restrictedOps.length;
- for (int k = 0; k < restrictedOpCount; k++) {
- if (restrictedOps[k]) {
- if (restrictedOpsValue.length() > 1) {
- restrictedOpsValue.append(", ");
- }
- restrictedOpsValue.append(AppOpsManager.opToName(k));
- }
- }
- restrictedOpsValue.append("]");
- pw.print(" "); pw.print("user: "); pw.print(userId);
- pw.print(" restricted ops: "); pw.println(restrictedOpsValue);
- }
- }
-
- final int excludedPackageCount = restrictionState.perUserExcludedPackageTags != null
- ? restrictionState.perUserExcludedPackageTags.size() : 0;
- if (excludedPackageCount > 0 && dumpOp < 0) {
- IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
- ipw.increaseIndent();
- boolean printedPackagesHeader = false;
- for (int j = 0; j < excludedPackageCount; j++) {
- int userId = restrictionState.perUserExcludedPackageTags.keyAt(j);
- PackageTagsList packageNames =
- restrictionState.perUserExcludedPackageTags.valueAt(j);
- if (packageNames == null) {
- continue;
- }
- boolean hasPackage;
- if (dumpPackage != null) {
- hasPackage = packageNames.includes(dumpPackage);
- } else {
- hasPackage = true;
- }
- if (!hasPackage) {
- continue;
- }
- if (!printedTokenHeader) {
- ipw.println("User restrictions for token " + token + ":");
- printedTokenHeader = true;
- }
-
- ipw.increaseIndent();
- if (!printedPackagesHeader) {
- ipw.println("Excluded packages:");
- printedPackagesHeader = true;
- }
-
- ipw.increaseIndent();
- ipw.print("user: ");
- ipw.print(userId);
- ipw.println(" packages: ");
-
- ipw.increaseIndent();
- packageNames.dump(ipw);
-
- ipw.decreaseIndent();
- ipw.decreaseIndent();
- ipw.decreaseIndent();
- }
- ipw.decreaseIndent();
- }
- }
+ boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
+ mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
if (!dumpHistory && !dumpWatchers) {
pw.println();
@@ -6085,8 +5971,6 @@
private final class ClientUserRestrictionState implements DeathRecipient {
private final IBinder token;
- SparseArray<boolean[]> perUserRestrictions;
- SparseArray<PackageTagsList> perUserExcludedPackageTags;
ClientUserRestrictionState(IBinder token)
throws RemoteException {
@@ -6096,134 +5980,29 @@
public boolean setRestriction(int code, boolean restricted,
PackageTagsList excludedPackageTags, int userId) {
- boolean changed = false;
-
- if (perUserRestrictions == null && restricted) {
- perUserRestrictions = new SparseArray<>();
- }
-
- int[] users;
- if (userId == UserHandle.USER_ALL) {
- // TODO(b/162888972): this call is returning all users, not just live ones - we
- // need to either fix the method called, or rename the variable
- List<UserInfo> liveUsers = UserManager.get(mContext).getUsers();
-
- users = new int[liveUsers.size()];
- for (int i = 0; i < liveUsers.size(); i++) {
- users[i] = liveUsers.get(i).id;
- }
- } else {
- users = new int[]{userId};
- }
-
- if (perUserRestrictions != null) {
- int numUsers = users.length;
-
- for (int i = 0; i < numUsers; i++) {
- int thisUserId = users[i];
-
- boolean[] userRestrictions = perUserRestrictions.get(thisUserId);
- if (userRestrictions == null && restricted) {
- userRestrictions = new boolean[AppOpsManager._NUM_OP];
- perUserRestrictions.put(thisUserId, userRestrictions);
- }
- if (userRestrictions != null && userRestrictions[code] != restricted) {
- userRestrictions[code] = restricted;
- if (!restricted && isDefault(userRestrictions)) {
- perUserRestrictions.remove(thisUserId);
- userRestrictions = null;
- }
- changed = true;
- }
-
- if (userRestrictions != null) {
- final boolean noExcludedPackages =
- excludedPackageTags == null || excludedPackageTags.isEmpty();
- if (perUserExcludedPackageTags == null && !noExcludedPackages) {
- perUserExcludedPackageTags = new SparseArray<>();
- }
- if (perUserExcludedPackageTags != null) {
- if (noExcludedPackages) {
- perUserExcludedPackageTags.remove(thisUserId);
- if (perUserExcludedPackageTags.size() <= 0) {
- perUserExcludedPackageTags = null;
- }
- } else {
- perUserExcludedPackageTags.put(thisUserId, excludedPackageTags);
- }
- changed = true;
- }
- }
- }
- }
-
- return changed;
+ return mAppOpsRestrictions.setUserRestriction(token, userId, code,
+ restricted, excludedPackageTags);
}
- public boolean hasRestriction(int restriction, String packageName, String attributionTag,
+ public boolean hasRestriction(int code, String packageName, String attributionTag,
int userId, boolean isCheckOp) {
- if (perUserRestrictions == null) {
- return false;
- }
- boolean[] restrictions = perUserRestrictions.get(userId);
- if (restrictions == null) {
- return false;
- }
- if (!restrictions[restriction]) {
- return false;
- }
- if (perUserExcludedPackageTags == null) {
- return true;
- }
- PackageTagsList perUserExclusions = perUserExcludedPackageTags.get(userId);
- if (perUserExclusions == null) {
- return true;
- }
-
- // TODO (b/240617242) add overload for checkOp to support attribution tags
- if (isCheckOp) {
- return !perUserExclusions.includes(packageName);
- }
- return !perUserExclusions.contains(packageName, attributionTag);
+ return mAppOpsRestrictions.getUserRestriction(token, userId, code, packageName,
+ attributionTag, isCheckOp);
}
public void removeUser(int userId) {
- if (perUserExcludedPackageTags != null) {
- perUserExcludedPackageTags.remove(userId);
- if (perUserExcludedPackageTags.size() <= 0) {
- perUserExcludedPackageTags = null;
- }
- }
- if (perUserRestrictions != null) {
- perUserRestrictions.remove(userId);
- if (perUserRestrictions.size() <= 0) {
- perUserRestrictions = null;
- }
- }
+ mAppOpsRestrictions.clearUserRestrictions(token, userId);
}
public boolean isDefault() {
- return perUserRestrictions == null || perUserRestrictions.size() <= 0;
+ return !mAppOpsRestrictions.hasUserRestrictions(token);
}
@Override
public void binderDied() {
synchronized (AppOpsService.this) {
+ mAppOpsRestrictions.clearUserRestrictions(token);
mOpUserRestrictions.remove(token);
- if (perUserRestrictions == null) {
- return;
- }
- final int userCount = perUserRestrictions.size();
- for (int i = 0; i < userCount; i++) {
- final boolean[] restrictions = perUserRestrictions.valueAt(i);
- final int restrictionCount = restrictions.length;
- for (int j = 0; j < restrictionCount; j++) {
- if (restrictions[j]) {
- final int changedCode = j;
- mHandler.post(() -> notifyWatchersOfChange(changedCode, UID_ANY));
- }
- }
- }
destroy();
}
}
@@ -6231,23 +6010,10 @@
public void destroy() {
token.unlinkToDeath(this, 0);
}
-
- private boolean isDefault(boolean[] array) {
- if (ArrayUtils.isEmpty(array)) {
- return true;
- }
- for (boolean value : array) {
- if (value) {
- return false;
- }
- }
- return true;
- }
}
private final class ClientGlobalRestrictionState implements DeathRecipient {
final IBinder mToken;
- final ArraySet<Integer> mRestrictedOps = new ArraySet<>();
ClientGlobalRestrictionState(IBinder token)
throws RemoteException {
@@ -6256,23 +6022,21 @@
}
boolean setRestriction(int code, boolean restricted) {
- if (restricted) {
- return mRestrictedOps.add(code);
- } else {
- return mRestrictedOps.remove(code);
- }
+ return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
}
boolean hasRestriction(int code) {
- return mRestrictedOps.contains(code);
+ return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
}
boolean isDefault() {
- return mRestrictedOps.isEmpty();
+ return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
}
@Override
public void binderDied() {
+ mAppOpsRestrictions.clearGlobalRestrictions(mToken);
+ mOpGlobalRestrictions.remove(mToken);
destroy();
}
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
index c4a9a4b..18f659e 100644
--- a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
@@ -148,6 +148,14 @@
/**
* Temporary API which will be removed once we can safely untangle the methods that use this.
* Notify that the app-op's mode is changed by triggering the change listener.
+ * @param op App-op whose mode has changed
+ * @param uid user id associated with the app-op (or, if UID_ANY, notifies all users)
+ */
+ void notifyWatchersOfChange(int op, int uid);
+
+ /**
+ * Temporary API which will be removed once we can safely untangle the methods that use this.
+ * Notify that the app-op's mode is changed by triggering the change listener.
* @param changedListener the change listener.
* @param op App-op whose mode has changed
* @param uid user id associated with the app-op
@@ -198,5 +206,4 @@
* @param printWriter writer to dump to.
*/
boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
-
}
diff --git a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java b/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
index 80266972..f6fff35 100644
--- a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
+++ b/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
@@ -333,6 +333,18 @@
}
@Override
+ public void notifyWatchersOfChange(int code, int uid) {
+ ArraySet<OnOpModeChangedListener> listenerSet = getOpModeChangedListeners(code);
+ if (listenerSet == null) {
+ return;
+ }
+ for (int i = 0; i < listenerSet.size(); i++) {
+ final OnOpModeChangedListener listener = listenerSet.valueAt(i);
+ notifyOpChanged(listener, code, uid, null);
+ }
+ }
+
+ @Override
public void notifyOpChanged(@NonNull OnOpModeChangedListener onModeChangedListener, int code,
int uid, @Nullable String packageName) {
Objects.requireNonNull(onModeChangedListener);
diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
index ab553a8..3ede0a2 100644
--- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
@@ -21,21 +21,23 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.radio.IRadioService;
-import android.util.Slog;
import com.android.server.SystemService;
+import java.util.ArrayList;
+
public class BroadcastRadioService extends SystemService {
- private static final String TAG = "BcRadioSrv";
private final IRadioService mServiceImpl;
+
public BroadcastRadioService(Context context) {
super(context);
- mServiceImpl = new BroadcastRadioServiceHidl(this);
+ ArrayList<String> serviceNameList = IRadioServiceAidlImpl.getServicesNames();
+ mServiceImpl = serviceNameList.isEmpty() ? new IRadioServiceHidlImpl(this)
+ : new IRadioServiceAidlImpl(this, serviceNameList);
}
@Override
public void onStart() {
- Slog.v(TAG, "BroadcastRadioService onStart()");
publishBinderService(Context.RADIO_SERVICE, mServiceImpl.asBinder());
}
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
new file mode 100644
index 0000000..0770062
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
@@ -0,0 +1,124 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio;
+
+import android.hardware.broadcastradio.IBroadcastRadio;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.hardware.radio.IRadioService;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.RadioManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import com.android.server.utils.Slogf;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Wrapper for AIDL interface for BroadcastRadio HAL
+ */
+final class IRadioServiceAidlImpl extends IRadioService.Stub {
+ private static final String TAG = "BcRadioSrvAidl";
+
+ private static final List<String> SERVICE_NAMES = Arrays.asList(
+ IBroadcastRadio.DESCRIPTOR + "/amfm", IBroadcastRadio.DESCRIPTOR + "/dab");
+
+ private final com.android.server.broadcastradio.aidl.BroadcastRadioServiceImpl mHalAidl;
+ private final BroadcastRadioService mService;
+
+ /**
+ * Gets names of all AIDL BroadcastRadio HAL services available.
+ */
+ public static ArrayList<String> getServicesNames() {
+ ArrayList<String> serviceList = new ArrayList<>();
+ for (int i = 0; i < SERVICE_NAMES.size(); i++) {
+ IBinder serviceBinder = ServiceManager.waitForDeclaredService(SERVICE_NAMES.get(i));
+ if (serviceBinder != null) {
+ serviceList.add(SERVICE_NAMES.get(i));
+ }
+ }
+ return serviceList;
+ }
+
+ IRadioServiceAidlImpl(BroadcastRadioService service, ArrayList<String> serviceList) {
+ Slogf.i(TAG, "Initialize BroadcastRadioServiceAidl(%s)", service);
+ mService = Objects.requireNonNull(service);
+ mHalAidl =
+ new com.android.server.broadcastradio.aidl.BroadcastRadioServiceImpl(serviceList);
+ }
+
+ @Override
+ public List<RadioManager.ModuleProperties> listModules() {
+ mService.enforcePolicyAccess();
+ return mHalAidl.listModules();
+ }
+
+ @Override
+ public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
+ boolean withAudio, ITunerCallback callback) throws RemoteException {
+ if (isDebugEnabled()) {
+ Slogf.d(TAG, "Opening module %d", moduleId);
+ }
+ mService.enforcePolicyAccess();
+ if (callback == null) {
+ throw new IllegalArgumentException("Callback must not be null");
+ }
+ return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback);
+ }
+
+ @Override
+ public ICloseHandle addAnnouncementListener(int[] enabledTypes,
+ IAnnouncementListener listener) {
+ if (isDebugEnabled()) {
+ Slogf.d(TAG, "Adding announcement listener for %s", Arrays.toString(enabledTypes));
+ }
+ Objects.requireNonNull(enabledTypes);
+ Objects.requireNonNull(listener);
+ mService.enforcePolicyAccess();
+
+ return mHalAidl.addAnnouncementListener(enabledTypes, listener);
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+ IndentingPrintWriter radioPrintWriter = new IndentingPrintWriter(printWriter);
+ radioPrintWriter.printf("BroadcastRadioService\n");
+
+ radioPrintWriter.increaseIndent();
+ radioPrintWriter.printf("AIDL HAL:\n");
+
+ radioPrintWriter.increaseIndent();
+ mHalAidl.dumpInfo(radioPrintWriter);
+ radioPrintWriter.decreaseIndent();
+
+ radioPrintWriter.decreaseIndent();
+ }
+
+ private static boolean isDebugEnabled() {
+ return Log.isLoggable(TAG, Log.DEBUG);
+ }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioServiceHidl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
similarity index 96%
rename from services/core/java/com/android/server/broadcastradio/BroadcastRadioServiceHidl.java
rename to services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
index 5cb6770..28b6d02 100644
--- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioServiceHidl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
@@ -41,7 +41,7 @@
/**
* Wrapper for HIDL interface for BroadcastRadio HAL
*/
-final class BroadcastRadioServiceHidl extends IRadioService.Stub {
+final class IRadioServiceHidlImpl extends IRadioService.Stub {
private static final String TAG = "BcRadioSrvHidl";
private final com.android.server.broadcastradio.hal1.BroadcastRadioService mHal1;
@@ -52,7 +52,7 @@
private final BroadcastRadioService mService;
private final List<RadioManager.ModuleProperties> mV1Modules;
- BroadcastRadioServiceHidl(BroadcastRadioService service) {
+ IRadioServiceHidlImpl(BroadcastRadioService service) {
mService = Objects.requireNonNull(service);
mHal1 = new com.android.server.broadcastradio.hal1.BroadcastRadioService(mLock);
mV1Modules = mHal1.loadModules();
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/AnnouncementAggregator.java b/services/core/java/com/android/server/broadcastradio/aidl/AnnouncementAggregator.java
new file mode 100644
index 0000000..b618aa3
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/aidl/AnnouncementAggregator.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+import android.annotation.Nullable;
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.utils.Slogf;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Announcement aggregator extending {@link ICloseHandle} to support broadcast radio announcement
+ */
+public final class AnnouncementAggregator extends ICloseHandle.Stub {
+ private static final String TAG = "BcRadioAidlSrv.AnnAggr";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final Object mLock;
+ private final IAnnouncementListener mListener;
+ private final IBinder.DeathRecipient mDeathRecipient = new DeathRecipient();
+
+ @GuardedBy("mLock")
+ private final List<ModuleWatcher> mModuleWatchers = new ArrayList<>();
+
+ @GuardedBy("mLock")
+ private boolean mIsClosed;
+
+ /**
+ * Constructs Announcement aggregator with AnnouncementListener of BroadcastRadio AIDL HAL.
+ */
+ public AnnouncementAggregator(IAnnouncementListener listener, Object lock) {
+ mListener = Objects.requireNonNull(listener, "listener cannot be null");
+ mLock = Objects.requireNonNull(lock, "lock cannot be null");
+ try {
+ listener.asBinder().linkToDeath(mDeathRecipient, /* flags= */ 0);
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ }
+
+ private final class ModuleWatcher extends IAnnouncementListener.Stub {
+
+ @Nullable
+ private ICloseHandle mCloseHandle;
+
+ public List<Announcement> mCurrentList = new ArrayList<>();
+
+ public void onListUpdated(List<Announcement> active) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onListUpdate for %s", active);
+ }
+ mCurrentList = Objects.requireNonNull(active, "active cannot be null");
+ AnnouncementAggregator.this.onListUpdated();
+ }
+
+ public void setCloseHandle(ICloseHandle closeHandle) {
+ if (DEBUG) {
+ Slogf.d(TAG, "Set close handle %s", closeHandle);
+ }
+ mCloseHandle = Objects.requireNonNull(closeHandle, "closeHandle cannot be null");
+ }
+
+ public void close() throws RemoteException {
+ if (DEBUG) {
+ Slogf.d(TAG, "Close module watcher.");
+ }
+ if (mCloseHandle != null) mCloseHandle.close();
+ }
+
+ public void dumpInfo(IndentingPrintWriter pw) {
+ pw.printf("ModuleWatcher:\n");
+
+ pw.increaseIndent();
+ pw.printf("Close handle: %s\n", mCloseHandle);
+ pw.printf("Current announcement list: %s\n", mCurrentList);
+ pw.decreaseIndent();
+ }
+ }
+
+ private class DeathRecipient implements IBinder.DeathRecipient {
+ public void binderDied() {
+ try {
+ close();
+ } catch (RemoteException ex) {
+ Slogf.e(TAG, ex, "Cannot close Announcement aggregator for DeathRecipient");
+ }
+ }
+ }
+
+ private void onListUpdated() {
+ if (DEBUG) {
+ Slogf.d(TAG, "onListUpdated()");
+ }
+ synchronized (mLock) {
+ if (mIsClosed) {
+ Slogf.e(TAG, "Announcement aggregator is closed, it shouldn't receive callbacks");
+ return;
+ }
+ List<Announcement> combined = new ArrayList<>(mModuleWatchers.size());
+ for (int i = 0; i < mModuleWatchers.size(); i++) {
+ combined.addAll(mModuleWatchers.get(i).mCurrentList);
+ }
+ try {
+ mListener.onListUpdated(combined);
+ } catch (RemoteException ex) {
+ Slogf.e(TAG, ex, "mListener.onListUpdated() failed");
+ }
+ }
+ }
+
+ /**
+ * Watches the given RadioModule by adding Announcement Listener to it
+ */
+ public void watchModule(RadioModule radioModule, int[] enabledTypes) {
+ if (DEBUG) {
+ Slogf.d(TAG, "Watch module for %s with enabled types %s",
+ radioModule, Arrays.toString(enabledTypes));
+ }
+ synchronized (mLock) {
+ if (mIsClosed) {
+ throw new IllegalStateException("Failed to watch module"
+ + "since announcement aggregator has already been closed");
+ }
+
+ ModuleWatcher watcher = new ModuleWatcher();
+ ICloseHandle closeHandle;
+ try {
+ closeHandle = radioModule.addAnnouncementListener(watcher, enabledTypes);
+ } catch (RemoteException ex) {
+ Slogf.e(TAG, ex, "Failed to add announcement listener");
+ return;
+ }
+ watcher.setCloseHandle(closeHandle);
+ mModuleWatchers.add(watcher);
+ }
+ }
+
+ @Override
+ public void close() throws RemoteException {
+ if (DEBUG) {
+ Slogf.d(TAG, "Close watchModule");
+ }
+ synchronized (mLock) {
+ if (mIsClosed) {
+ Slogf.w(TAG, "Announcement aggregator has already been closed.");
+ return;
+ }
+
+ mListener.asBinder().unlinkToDeath(mDeathRecipient, /* flags= */ 0);
+
+ for (int i = 0; i < mModuleWatchers.size(); i++) {
+ ModuleWatcher moduleWatcher = mModuleWatchers.get(i);
+ try {
+ moduleWatcher.close();
+ } catch (Exception e) {
+ Slogf.e(TAG, "Failed to close module watcher %s: %s",
+ moduleWatcher, e);
+ }
+ }
+ mModuleWatchers.clear();
+
+ mIsClosed = true;
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+ IndentingPrintWriter announcementPrintWriter = new IndentingPrintWriter(printWriter);
+ announcementPrintWriter.printf("AnnouncementAggregator\n");
+
+ announcementPrintWriter.increaseIndent();
+ synchronized (mLock) {
+ announcementPrintWriter.printf("Is session closed? %s\n", mIsClosed ? "Yes" : "No");
+ announcementPrintWriter.printf("Module Watchers [%d]:\n", mModuleWatchers.size());
+
+ announcementPrintWriter.increaseIndent();
+ for (int i = 0; i < mModuleWatchers.size(); i++) {
+ mModuleWatchers.get(i).dumpInfo(announcementPrintWriter);
+ }
+ announcementPrintWriter.decreaseIndent();
+
+ }
+ announcementPrintWriter.decreaseIndent();
+ }
+
+}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
new file mode 100644
index 0000000..71ba296
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+import android.annotation.Nullable;
+import android.hardware.broadcastradio.IBroadcastRadio;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioTuner;
+import android.os.IBinder;
+import android.os.IServiceCallback;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.utils.Slogf;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Broadcast radio service using BroadcastRadio AIDL HAL
+ */
+public final class BroadcastRadioServiceImpl {
+ private static final String TAG = "BcRadioAidlSrv";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private int mNextModuleId;
+
+ @GuardedBy("mLock")
+ private final Map<String, Integer> mServiceNameToModuleIdMap = new ArrayMap<>();
+
+ // Map from module ID to RadioModule created by mServiceListener.onRegistration().
+ @GuardedBy("mLock")
+ private final SparseArray<RadioModule> mModules = new SparseArray<>();
+
+ private final IServiceCallback.Stub mServiceListener = new IServiceCallback.Stub() {
+ @Override
+ public void onRegistration(String name, final IBinder newBinder) {
+ Slogf.i(TAG, "onRegistration for %s", name);
+ Integer moduleId;
+ synchronized (mLock) {
+ // If the service has been registered before, reuse its previous module ID.
+ moduleId = mServiceNameToModuleIdMap.get(name);
+ boolean newService = false;
+ if (moduleId == null) {
+ newService = true;
+ moduleId = mNextModuleId;
+ }
+
+ RadioModule radioModule =
+ RadioModule.tryLoadingModule(moduleId, name, newBinder, mLock);
+ if (radioModule == null) {
+ Slogf.w(TAG, "No module %s with id %d (HAL AIDL)", name, moduleId);
+ return;
+ }
+ try {
+ radioModule.setInternalHalCallback();
+ } catch (RemoteException ex) {
+ Slogf.wtf(TAG, ex, "Broadcast radio module %s with id %d (HAL AIDL) "
+ + "cannot register HAL callback", name, moduleId);
+ return;
+ }
+ if (DEBUG) {
+ Slogf.d(TAG, "Loaded broadcast radio module %s with id %d (HAL AIDL)",
+ name, moduleId);
+ }
+ RadioModule prevModule = mModules.get(moduleId);
+ mModules.put(moduleId, radioModule);
+ if (prevModule != null) {
+ prevModule.closeSessions(RadioTuner.ERROR_HARDWARE_FAILURE);
+ }
+
+ if (newService) {
+ mServiceNameToModuleIdMap.put(name, moduleId);
+ mNextModuleId++;
+ }
+
+ try {
+ BroadcastRadioDeathRecipient deathRecipient =
+ new BroadcastRadioDeathRecipient(moduleId);
+ radioModule.getService().asBinder().linkToDeath(deathRecipient, moduleId);
+ } catch (RemoteException ex) {
+ Slogf.w(TAG, "Service has already died, so remove its entry from mModules.");
+ mModules.remove(moduleId);
+ }
+ }
+ }
+ };
+
+ private final class BroadcastRadioDeathRecipient implements IBinder.DeathRecipient {
+ private final int mModuleId;
+
+ BroadcastRadioDeathRecipient(int moduleId) {
+ mModuleId = moduleId;
+ }
+
+ @Override
+ public void binderDied() {
+ Slogf.i(TAG, "ServiceDied for module id %d", mModuleId);
+ synchronized (mLock) {
+ RadioModule prevModule = mModules.removeReturnOld(mModuleId);
+ if (prevModule != null) {
+ prevModule.closeSessions(RadioTuner.ERROR_HARDWARE_FAILURE);
+ }
+
+ for (Map.Entry<String, Integer> entry : mServiceNameToModuleIdMap.entrySet()) {
+ if (entry.getValue() == mModuleId) {
+ Slogf.w(TAG, "Service %s died, removed RadioModule with ID %d",
+ entry.getKey(), mModuleId);
+ return;
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * Constructs BroadcastRadioServiceImpl using AIDL HAL using the list of names of AIDL
+ * BroadcastRadio HAL services {@code serviceNameList}
+ */
+ public BroadcastRadioServiceImpl(ArrayList<String> serviceNameList) {
+ mNextModuleId = 0;
+ if (DEBUG) {
+ Slogf.d(TAG, "Initializing BroadcastRadioServiceImpl %s",
+ IBroadcastRadio.DESCRIPTOR);
+ }
+ for (int i = 0; i < serviceNameList.size(); i++) {
+ try {
+ ServiceManager.registerForNotifications(serviceNameList.get(i), mServiceListener);
+ } catch (RemoteException ex) {
+ Slogf.e(TAG, ex, "failed to register for service notifications for service %s",
+ serviceNameList.get(i));
+ }
+ }
+ }
+
+ /**
+ * Gets all AIDL {@link com.android.server.broadcastradio.aidl.RadioModule}.
+ */
+ public List<RadioManager.ModuleProperties> listModules() {
+ synchronized (mLock) {
+ List<RadioManager.ModuleProperties> moduleList = new ArrayList<>(mModules.size());
+ for (int i = 0; i < mModules.size(); i++) {
+ moduleList.add(mModules.valueAt(i).mProperties);
+ }
+ return moduleList;
+ }
+ }
+
+ /**
+ * Gets the AIDL RadioModule for the given {@code moduleId}. Null will be returned if not found.
+ */
+ public boolean hasModule(int id) {
+ synchronized (mLock) {
+ return mModules.contains(id);
+ }
+ }
+
+ /**
+ * Returns whether any AIDL {@link com.android.server.broadcastradio.aidl.RadioModule} exists.
+ */
+ public boolean hasAnyModules() {
+ synchronized (mLock) {
+ return mModules.size() != 0;
+ }
+ }
+
+ /**
+ * Opens {@link ITuner} session for the AIDL
+ * {@link com.android.server.broadcastradio.aidl.RadioModule} given {@code moduleId}.
+ */
+ @Nullable
+ public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
+ boolean withAudio, ITunerCallback callback) throws RemoteException {
+ if (DEBUG) {
+ Slogf.d(TAG, "Open AIDL radio session");
+ }
+ Objects.requireNonNull(callback);
+
+ if (!withAudio) {
+ throw new IllegalArgumentException("Non-audio sessions not supported with AIDL HAL");
+ }
+
+ RadioModule radioModule;
+ synchronized (mLock) {
+ radioModule = mModules.get(moduleId);
+ if (radioModule == null) {
+ Slogf.e(TAG, "Invalid module ID %d", moduleId);
+ return null;
+ }
+ }
+
+ TunerSession tunerSession = radioModule.openSession(callback);
+ if (legacyConfig != null) {
+ tunerSession.setConfiguration(legacyConfig);
+ }
+ return tunerSession;
+ }
+
+ /**
+ * Adds AnnouncementListener for every
+ * {@link com.android.server.broadcastradio.aidl.RadioModule}.
+ */
+ public ICloseHandle addAnnouncementListener(int[] enabledTypes,
+ IAnnouncementListener listener) {
+ if (DEBUG) {
+ Slogf.d(TAG, "Add AnnouncementListener with enable types %s",
+ Arrays.toString(enabledTypes));
+ }
+ AnnouncementAggregator aggregator = new AnnouncementAggregator(listener, mLock);
+ boolean anySupported = false;
+ synchronized (mLock) {
+ for (int i = 0; i < mModules.size(); i++) {
+ try {
+ aggregator.watchModule(mModules.valueAt(i), enabledTypes);
+ anySupported = true;
+ } catch (UnsupportedOperationException ex) {
+ Slogf.w(TAG, ex, "Announcements not supported for this module");
+ }
+ }
+ }
+ if (!anySupported) {
+ Slogf.w(TAG, "There are no HAL modules that support announcements");
+ }
+ return aggregator;
+ }
+
+ /**
+ * Dump state of broadcastradio service for AIDL HAL.
+ *
+ * @param pw The file to which {@link BroadcastRadioServiceImpl} state is dumped.
+ */
+ public void dumpInfo(IndentingPrintWriter pw) {
+ synchronized (mLock) {
+ pw.printf("Next module id available: %d\n", mNextModuleId);
+ pw.printf("ServiceName to module id map:\n");
+
+ pw.increaseIndent();
+ for (Map.Entry<String, Integer> entry : mServiceNameToModuleIdMap.entrySet()) {
+ pw.printf("Service name: %s, module id: %d\n", entry.getKey(), entry.getValue());
+ }
+ pw.decreaseIndent();
+
+ pw.printf("Radio modules [%d]:\n", mModules.size());
+
+ pw.increaseIndent();
+ for (int i = 0; i < mModules.size(); i++) {
+ pw.printf("Module id=%d:\n", mModules.keyAt(i));
+
+ pw.increaseIndent();
+ mModules.valueAt(i).dumpInfo(pw);
+ pw.decreaseIndent();
+ }
+ pw.decreaseIndent();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
new file mode 100644
index 0000000..d90f9c4
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+import android.annotation.Nullable;
+import android.hardware.broadcastradio.AmFmRegionConfig;
+import android.hardware.broadcastradio.Announcement;
+import android.hardware.broadcastradio.DabTableEntry;
+import android.hardware.broadcastradio.IdentifierType;
+import android.hardware.broadcastradio.Metadata;
+import android.hardware.broadcastradio.ProgramFilter;
+import android.hardware.broadcastradio.ProgramIdentifier;
+import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.broadcastradio.ProgramListChunk;
+import android.hardware.broadcastradio.Properties;
+import android.hardware.broadcastradio.Result;
+import android.hardware.broadcastradio.VendorKeyValue;
+import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioMetadata;
+import android.os.ParcelableException;
+import android.os.ServiceSpecificException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IntArray;
+
+import com.android.server.utils.Slogf;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A utils class converting data types between AIDL broadcast radio HAL and
+ * {@link android.hardware.radio}
+ */
+final class ConversionUtils {
+ // TODO(b/241118988): Add unit test for ConversionUtils class
+ private static final String TAG = "BcRadioAidlSrv.convert";
+
+ private ConversionUtils() {
+ throw new UnsupportedOperationException("ConversionUtils class is noninstantiable");
+ }
+
+ static RuntimeException throwOnError(RuntimeException halException, String action) {
+ if (!(halException instanceof ServiceSpecificException)) {
+ return new ParcelableException(new RuntimeException(
+ action + ": unknown error"));
+ }
+ int result = ((ServiceSpecificException) halException).errorCode;
+ switch (result) {
+ case Result.UNKNOWN_ERROR:
+ return new ParcelableException(new RuntimeException(action
+ + ": UNKNOWN_ERROR"));
+ case Result.INTERNAL_ERROR:
+ return new ParcelableException(new RuntimeException(action
+ + ": INTERNAL_ERROR"));
+ case Result.INVALID_ARGUMENTS:
+ return new IllegalArgumentException(action + ": INVALID_ARGUMENTS");
+ case Result.INVALID_STATE:
+ return new IllegalStateException(action + ": INVALID_STATE");
+ case Result.NOT_SUPPORTED:
+ return new UnsupportedOperationException(action + ": NOT_SUPPORTED");
+ case Result.TIMEOUT:
+ return new ParcelableException(new RuntimeException(action + ": TIMEOUT"));
+ default:
+ return new ParcelableException(new RuntimeException(
+ action + ": unknown error (" + result + ")"));
+ }
+ }
+
+ static VendorKeyValue[] vendorInfoToHalVendorKeyValues(@Nullable Map<String, String> info) {
+ if (info == null) {
+ return new VendorKeyValue[]{};
+ }
+
+ ArrayList<VendorKeyValue> list = new ArrayList<>();
+ for (Map.Entry<String, String> entry : info.entrySet()) {
+ VendorKeyValue elem = new VendorKeyValue();
+ elem.key = entry.getKey();
+ elem.value = entry.getValue();
+ if (elem.key == null || elem.value == null) {
+ Slogf.w(TAG, "VendorKeyValue contains invalid entry: key = %s, value = %s",
+ elem.key, elem.value);
+ continue;
+ }
+ list.add(elem);
+ }
+
+ return list.toArray(VendorKeyValue[]::new);
+ }
+
+ static Map<String, String> vendorInfoFromHalVendorKeyValues(@Nullable VendorKeyValue[] info) {
+ if (info == null) {
+ return Collections.emptyMap();
+ }
+
+ Map<String, String> map = new ArrayMap<>();
+ for (VendorKeyValue kvp : info) {
+ if (kvp.key == null || kvp.value == null) {
+ Slogf.w(TAG, "VendorKeyValue contains invalid entry: key = %s, value = %s",
+ kvp.key, kvp.value);
+ continue;
+ }
+ map.put(kvp.key, kvp.value);
+ }
+
+ return map;
+ }
+
+ @ProgramSelector.ProgramType
+ private static int identifierTypeToProgramType(
+ @ProgramSelector.IdentifierType int idType) {
+ switch (idType) {
+ case ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY:
+ case ProgramSelector.IDENTIFIER_TYPE_RDS_PI:
+ // TODO(b/69958423): verify AM/FM with frequency range
+ return ProgramSelector.PROGRAM_TYPE_FM;
+ case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT:
+ // TODO(b/69958423): verify AM/FM with frequency range
+ return ProgramSelector.PROGRAM_TYPE_FM_HD;
+ case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC:
+ case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE:
+ case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID:
+ case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY:
+ return ProgramSelector.PROGRAM_TYPE_DAB;
+ case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID:
+ case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY:
+ return ProgramSelector.PROGRAM_TYPE_DRMO;
+ case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID:
+ case ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL:
+ return ProgramSelector.PROGRAM_TYPE_SXM;
+ }
+ if (idType >= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_START
+ && idType <= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_END) {
+ return idType;
+ }
+ return ProgramSelector.PROGRAM_TYPE_INVALID;
+ }
+
+ private static int[] identifierTypesToProgramTypes(int[] idTypes) {
+ Set<Integer> programTypes = new ArraySet<>();
+
+ for (int i = 0; i < idTypes.length; i++) {
+ int pType = identifierTypeToProgramType(idTypes[i]);
+
+ if (pType == ProgramSelector.PROGRAM_TYPE_INVALID) continue;
+
+ programTypes.add(pType);
+ if (pType == ProgramSelector.PROGRAM_TYPE_FM) {
+ // TODO(b/69958423): verify AM/FM with region info
+ programTypes.add(ProgramSelector.PROGRAM_TYPE_AM);
+ }
+ if (pType == ProgramSelector.PROGRAM_TYPE_FM_HD) {
+ // TODO(b/69958423): verify AM/FM with region info
+ programTypes.add(ProgramSelector.PROGRAM_TYPE_AM_HD);
+ }
+ }
+
+ int[] programTypesArray = new int[programTypes.size()];
+ int i = 0;
+ for (int programType : programTypes) {
+ programTypesArray[i++] = programType;
+ }
+ return programTypesArray;
+ }
+
+ private static RadioManager.BandDescriptor[] amfmConfigToBands(
+ @Nullable AmFmRegionConfig config) {
+ if (config == null) {
+ return new RadioManager.BandDescriptor[0];
+ }
+
+ int len = config.ranges.length;
+ List<RadioManager.BandDescriptor> bands = new ArrayList<>();
+
+ // Just a placeholder value.
+ int region = RadioManager.REGION_ITU_1;
+
+ for (int i = 0; i < len; i++) {
+ Utils.FrequencyBand bandType = Utils.getBand(config.ranges[i].lowerBound);
+ if (bandType == Utils.FrequencyBand.UNKNOWN) {
+ Slogf.e(TAG, "Unknown frequency band at %d kHz", config.ranges[i].lowerBound);
+ continue;
+ }
+ if (bandType == Utils.FrequencyBand.FM) {
+ bands.add(new RadioManager.FmBandDescriptor(region, RadioManager.BAND_FM,
+ config.ranges[i].lowerBound, config.ranges[i].upperBound,
+ config.ranges[i].spacing,
+
+ // TODO(b/69958777): stereo, rds, ta, af, ea
+ /* stereo= */ true, /* rds= */ true, /* ta= */ true, /* af= */ true,
+ /* ea= */ true
+ ));
+ } else { // AM
+ bands.add(new RadioManager.AmBandDescriptor(region, RadioManager.BAND_AM,
+ config.ranges[i].lowerBound, config.ranges[i].upperBound,
+ config.ranges[i].spacing,
+
+ // TODO(b/69958777): stereo
+ /* stereo= */ true
+ ));
+ }
+ }
+
+ return bands.toArray(RadioManager.BandDescriptor[]::new);
+ }
+
+ @Nullable
+ private static Map<String, Integer> dabConfigFromHalDabTableEntries(
+ @Nullable DabTableEntry[] config) {
+ if (config == null) {
+ return null;
+ }
+ Map<String, Integer> dabConfig = new ArrayMap<>();
+ for (int i = 0; i < config.length; i++) {
+ dabConfig.put(config[i].label, config[i].frequencyKhz);
+ }
+ return dabConfig;
+ }
+
+ static RadioManager.ModuleProperties propertiesFromHalProperties(int id,
+ String serviceName, Properties prop,
+ @Nullable AmFmRegionConfig amfmConfig, @Nullable DabTableEntry[] dabConfig) {
+ Objects.requireNonNull(serviceName);
+ Objects.requireNonNull(prop);
+
+ int[] supportedProgramTypes = identifierTypesToProgramTypes(prop.supportedIdentifierTypes);
+
+ return new RadioManager.ModuleProperties(
+ id,
+ serviceName,
+
+ // There is no Class concept in HAL AIDL.
+ RadioManager.CLASS_AM_FM,
+
+ prop.maker,
+ prop.product,
+ prop.version,
+ prop.serial,
+
+ // HAL AIDL only supports single tuner and audio source per
+ // HAL implementation instance.
+ /* numTuners= */ 1,
+ /* numAudioSources= */ 1,
+ /* isInitializationRequired= */ false,
+ /* isCaptureSupported= */ false,
+
+ amfmConfigToBands(amfmConfig),
+ /* isBgScanSupported= */ true,
+ supportedProgramTypes,
+ prop.supportedIdentifierTypes,
+ dabConfigFromHalDabTableEntries(dabConfig),
+ vendorInfoFromHalVendorKeyValues(prop.vendorInfo)
+ );
+ }
+
+ static ProgramIdentifier identifierToHalProgramIdentifier(ProgramSelector.Identifier id) {
+ ProgramIdentifier hwId = new ProgramIdentifier();
+ hwId.type = id.getType();
+ hwId.value = id.getValue();
+ return hwId;
+ }
+
+ @Nullable
+ static ProgramSelector.Identifier identifierFromHalProgramIdentifier(
+ ProgramIdentifier id) {
+ if (id.type == IdentifierType.INVALID) {
+ return null;
+ }
+ return new ProgramSelector.Identifier(id.type, id.value);
+ }
+
+ static android.hardware.broadcastradio.ProgramSelector programSelectorToHalProgramSelector(
+ ProgramSelector sel) {
+ android.hardware.broadcastradio.ProgramSelector hwSel =
+ new android.hardware.broadcastradio.ProgramSelector();
+
+ hwSel.primaryId = identifierToHalProgramIdentifier(sel.getPrimaryId());
+ ProgramSelector.Identifier[] secondaryIds = sel.getSecondaryIds();
+ ArrayList<ProgramIdentifier> secondaryIdList = new ArrayList<>(secondaryIds.length);
+ for (int i = 0; i < secondaryIds.length; i++) {
+ secondaryIdList.add(identifierToHalProgramIdentifier(secondaryIds[i]));
+ }
+ hwSel.secondaryIds = secondaryIdList.toArray(ProgramIdentifier[]::new);
+ return hwSel;
+ }
+
+ private static boolean isEmpty(
+ android.hardware.broadcastradio.ProgramSelector sel) {
+ return sel.primaryId.type == IdentifierType.INVALID && sel.primaryId.value == 0
+ && sel.secondaryIds.length == 0;
+ }
+
+ @Nullable
+ static ProgramSelector programSelectorFromHalProgramSelector(
+ android.hardware.broadcastradio.ProgramSelector sel) {
+ if (isEmpty(sel)) {
+ return null;
+ }
+
+ List<ProgramSelector.Identifier> secondaryIdList = new ArrayList<>();
+ for (int i = 0; i < sel.secondaryIds.length; i++) {
+ if (sel.secondaryIds[i] != null) {
+ secondaryIdList.add(identifierFromHalProgramIdentifier(sel.secondaryIds[i]));
+ }
+ }
+
+ return new ProgramSelector(
+ identifierTypeToProgramType(sel.primaryId.type),
+ Objects.requireNonNull(identifierFromHalProgramIdentifier(sel.primaryId)),
+ secondaryIdList.toArray(new ProgramSelector.Identifier[0]),
+ /* vendorIds= */ null);
+ }
+
+ private static RadioMetadata radioMetadataFromHalMetadata(Metadata[] meta) {
+ RadioMetadata.Builder builder = new RadioMetadata.Builder();
+
+ for (int i = 0; i < meta.length; i++) {
+ switch (meta[i].getTag()) {
+ case Metadata.rdsPs:
+ builder.putString(RadioMetadata.METADATA_KEY_RDS_PS, meta[i].getRdsPs());
+ break;
+ case Metadata.rdsPty:
+ builder.putInt(RadioMetadata.METADATA_KEY_RDS_PTY, meta[i].getRdsPty());
+ break;
+ case Metadata.rbdsPty:
+ builder.putInt(RadioMetadata.METADATA_KEY_RBDS_PTY, meta[i].getRbdsPty());
+ break;
+ case Metadata.rdsRt:
+ builder.putString(RadioMetadata.METADATA_KEY_RDS_RT, meta[i].getRdsRt());
+ break;
+ case Metadata.songTitle:
+ builder.putString(RadioMetadata.METADATA_KEY_TITLE, meta[i].getSongTitle());
+ break;
+ case Metadata.songArtist:
+ builder.putString(RadioMetadata.METADATA_KEY_ARTIST, meta[i].getSongArtist());
+ break;
+ case Metadata.songAlbum:
+ builder.putString(RadioMetadata.METADATA_KEY_ALBUM, meta[i].getSongAlbum());
+ break;
+ case Metadata.stationIcon:
+ builder.putInt(RadioMetadata.METADATA_KEY_ICON, meta[i].getStationIcon());
+ break;
+ case Metadata.albumArt:
+ builder.putInt(RadioMetadata.METADATA_KEY_ART, meta[i].getAlbumArt());
+ break;
+ case Metadata.programName:
+ builder.putString(RadioMetadata.METADATA_KEY_PROGRAM_NAME,
+ meta[i].getProgramName());
+ break;
+ case Metadata.dabEnsembleName:
+ builder.putString(RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME,
+ meta[i].getDabEnsembleName());
+ break;
+ case Metadata.dabEnsembleNameShort:
+ builder.putString(RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT,
+ meta[i].getDabEnsembleNameShort());
+ break;
+ case Metadata.dabServiceName:
+ builder.putString(RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME,
+ meta[i].getDabServiceName());
+ break;
+ case Metadata.dabServiceNameShort:
+ builder.putString(RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME_SHORT,
+ meta[i].getDabServiceNameShort());
+ break;
+ case Metadata.dabComponentName:
+ builder.putString(RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME,
+ meta[i].getDabComponentName());
+ break;
+ case Metadata.dabComponentNameShort:
+ builder.putString(RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME_SHORT,
+ meta[i].getDabComponentNameShort());
+ break;
+ default:
+ Slogf.w(TAG, "Ignored unknown metadata entry: %s", meta[i]);
+ break;
+ }
+
+ }
+
+ return builder.build();
+ }
+
+ static RadioManager.ProgramInfo programInfoFromHalProgramInfo(ProgramInfo info) {
+ Collection<ProgramSelector.Identifier> relatedContent = new ArrayList<>();
+ if (info.relatedContent != null) {
+ for (int i = 0; i < info.relatedContent.length; i++) {
+ ProgramSelector.Identifier relatedContentId =
+ identifierFromHalProgramIdentifier(info.relatedContent[i]);
+ if (relatedContentId != null) {
+ relatedContent.add(relatedContentId);
+ }
+ }
+ }
+
+ return new RadioManager.ProgramInfo(
+ Objects.requireNonNull(programSelectorFromHalProgramSelector(info.selector)),
+ identifierFromHalProgramIdentifier(info.logicallyTunedTo),
+ identifierFromHalProgramIdentifier(info.physicallyTunedTo),
+ relatedContent,
+ info.infoFlags,
+ info.signalQuality,
+ radioMetadataFromHalMetadata(info.metadata),
+ vendorInfoFromHalVendorKeyValues(info.vendorInfo)
+ );
+ }
+
+ static ProgramFilter filterToHalProgramFilter(@Nullable ProgramList.Filter filter) {
+ if (filter == null) {
+ filter = new ProgramList.Filter();
+ }
+
+ ProgramFilter hwFilter = new ProgramFilter();
+
+ IntArray identifierTypeList = new IntArray(filter.getIdentifierTypes().size());
+ ArrayList<ProgramIdentifier> identifiersList = new ArrayList<>();
+ Iterator<Integer> typeIterator = filter.getIdentifierTypes().iterator();
+ while (typeIterator.hasNext()) {
+ identifierTypeList.add(typeIterator.next());
+ }
+ Iterator<ProgramSelector.Identifier> idIterator = filter.getIdentifiers().iterator();
+ while (idIterator.hasNext()) {
+ identifiersList.add(identifierToHalProgramIdentifier(idIterator.next()));
+ }
+
+ hwFilter.identifierTypes = identifierTypeList.toArray();
+ hwFilter.identifiers = identifiersList.toArray(ProgramIdentifier[]::new);
+ hwFilter.includeCategories = filter.areCategoriesIncluded();
+ hwFilter.excludeModifications = filter.areModificationsExcluded();
+
+ return hwFilter;
+ }
+
+ static ProgramList.Chunk chunkFromHalProgramListChunk(ProgramListChunk chunk) {
+ Set<RadioManager.ProgramInfo> modified = new ArraySet<>(chunk.modified.length);
+ for (int i = 0; i < chunk.modified.length; i++) {
+ modified.add(programInfoFromHalProgramInfo(chunk.modified[i]));
+ }
+ Set<ProgramSelector.Identifier> removed = new ArraySet<>();
+ if (chunk.removed != null) {
+ for (int i = 0; i < chunk.removed.length; i++) {
+ ProgramSelector.Identifier removedId =
+ identifierFromHalProgramIdentifier(chunk.removed[i]);
+ if (removedId != null) {
+ removed.add(removedId);
+ }
+ }
+ }
+ return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed);
+ }
+
+ public static android.hardware.radio.Announcement announcementFromHalAnnouncement(
+ Announcement hwAnnouncement) {
+ return new android.hardware.radio.Announcement(
+ Objects.requireNonNull(programSelectorFromHalProgramSelector(
+ hwAnnouncement.selector)),
+ hwAnnouncement.type,
+ vendorInfoFromHalVendorKeyValues(hwAnnouncement.vendorInfo)
+ );
+ }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java b/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java
new file mode 100644
index 0000000..095a5fa
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+import android.annotation.Nullable;
+import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class to filter and update program info for HAL clients from broadcast radio AIDL HAL
+ */
+final class ProgramInfoCache {
+
+ /**
+ * Maximum number of {@link RadioManager#ProgramInfo} elements that will be put into a
+ * ProgramList.Chunk.mModified array. Used to try to ensure a single ProgramList.Chunk
+ * stays within the AIDL data size limit.
+ */
+ private static final int MAX_NUM_MODIFIED_PER_CHUNK = 100;
+
+ /**
+ * Maximum number of {@link ProgramSelector#Identifier} elements that will be put
+ * into the removed array of {@link ProgramList#Chunk}. Used to try to ensure a single
+ * {@link ProgramList#Chunk} stays within the AIDL data size limit.
+ */
+ private static final int MAX_NUM_REMOVED_PER_CHUNK = 500;
+
+ /**
+ * Map from primary identifier to corresponding {@link RadioManager#ProgramInfo}.
+ */
+ private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mProgramInfoMap =
+ new ArrayMap<>();
+
+ /**
+ * Flag indicating whether mProgramInfoMap is considered complete based upon the received
+ * updates.
+ */
+ private boolean mComplete = true;
+
+ /**
+ * Optional filter used in {@link ProgramInfoCache#filterAndUpdateFromInternal}. Usually this
+ * field is null for a HAL-side cache and non-null for an AIDL-side cache.
+ */
+ @Nullable private final ProgramList.Filter mFilter;
+
+ ProgramInfoCache(@Nullable ProgramList.Filter filter) {
+ mFilter = filter;
+ }
+
+ @VisibleForTesting
+ ProgramInfoCache(@Nullable ProgramList.Filter filter, boolean complete,
+ RadioManager.ProgramInfo... programInfos) {
+ mFilter = filter;
+ mComplete = complete;
+ for (int i = 0; i < programInfos.length; i++) {
+ mProgramInfoMap.put(programInfos[i].getSelector().getPrimaryId(), programInfos[i]);
+ }
+ }
+
+ @VisibleForTesting
+ boolean programInfosAreExactly(RadioManager.ProgramInfo... programInfos) {
+ Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> expectedMap = new ArrayMap<>();
+ for (int i = 0; i < programInfos.length; i++) {
+ expectedMap.put(programInfos[i].getSelector().getPrimaryId(), programInfos[i]);
+ }
+ return expectedMap.equals(mProgramInfoMap);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("ProgramInfoCache(mComplete = ");
+ sb.append(mComplete);
+ sb.append(", mFilter = ");
+ sb.append(mFilter);
+ sb.append(", mProgramInfoMap = [");
+ mProgramInfoMap.forEach((id, programInfo) -> {
+ sb.append(", ");
+ sb.append(programInfo);
+ });
+ return sb.append("])").toString();
+ }
+
+ public boolean isComplete() {
+ return mComplete;
+ }
+
+ @Nullable
+ public ProgramList.Filter getFilter() {
+ return mFilter;
+ }
+
+ @VisibleForTesting
+ void updateFromHalProgramListChunk(
+ android.hardware.broadcastradio.ProgramListChunk chunk) {
+ if (chunk.purge) {
+ mProgramInfoMap.clear();
+ }
+ for (int i = 0; i < chunk.modified.length; i++) {
+ RadioManager.ProgramInfo programInfo =
+ ConversionUtils.programInfoFromHalProgramInfo(chunk.modified[i]);
+ mProgramInfoMap.put(programInfo.getSelector().getPrimaryId(), programInfo);
+ }
+ if (chunk.removed != null) {
+ for (int i = 0; i < chunk.removed.length; i++) {
+ mProgramInfoMap.remove(
+ ConversionUtils.identifierFromHalProgramIdentifier(chunk.removed[i]));
+ }
+ }
+ mComplete = chunk.complete;
+ }
+
+ List<ProgramList.Chunk> filterAndUpdateFromInternal(ProgramInfoCache other,
+ boolean purge) {
+ return filterAndUpdateFromInternal(other, purge, MAX_NUM_MODIFIED_PER_CHUNK,
+ MAX_NUM_REMOVED_PER_CHUNK);
+ }
+
+ @VisibleForTesting
+ List<ProgramList.Chunk> filterAndUpdateFromInternal(ProgramInfoCache other,
+ boolean purge, int maxNumModifiedPerChunk, int maxNumRemovedPerChunk) {
+ if (purge) {
+ mProgramInfoMap.clear();
+ }
+ // If mProgramInfoMap is empty, we treat this update as a purge because this might be the
+ // first update to an AIDL client that changed its filter.
+ if (mProgramInfoMap.isEmpty()) {
+ purge = true;
+ }
+
+ Set<RadioManager.ProgramInfo> modified = new ArraySet<>();
+ Set<ProgramSelector.Identifier> removed = new ArraySet<>(mProgramInfoMap.keySet());
+ for (Map.Entry<ProgramSelector.Identifier, RadioManager.ProgramInfo> entry
+ : other.mProgramInfoMap.entrySet()) {
+ ProgramSelector.Identifier id = entry.getKey();
+ if (!passesFilter(id)) {
+ continue;
+ }
+ removed.remove(id);
+
+ RadioManager.ProgramInfo newInfo = entry.getValue();
+ if (!shouldIncludeInModified(newInfo)) {
+ continue;
+ }
+ mProgramInfoMap.put(id, newInfo);
+ modified.add(newInfo);
+ }
+ for (ProgramSelector.Identifier rem : removed) {
+ mProgramInfoMap.remove(rem);
+ }
+ mComplete = other.mComplete;
+ return buildChunks(purge, mComplete, modified, maxNumModifiedPerChunk, removed,
+ maxNumRemovedPerChunk);
+ }
+
+ @Nullable
+ List<ProgramList.Chunk> filterAndApplyChunk(ProgramList.Chunk chunk) {
+ return filterAndApplyChunkInternal(chunk, MAX_NUM_MODIFIED_PER_CHUNK,
+ MAX_NUM_REMOVED_PER_CHUNK);
+ }
+
+ @VisibleForTesting
+ @Nullable
+ List<ProgramList.Chunk> filterAndApplyChunkInternal(ProgramList.Chunk chunk,
+ int maxNumModifiedPerChunk, int maxNumRemovedPerChunk) {
+ if (chunk.isPurge()) {
+ mProgramInfoMap.clear();
+ }
+
+ Set<RadioManager.ProgramInfo> modified = new ArraySet<>();
+ Set<ProgramSelector.Identifier> removed = new ArraySet<>();
+ for (RadioManager.ProgramInfo info : chunk.getModified()) {
+ ProgramSelector.Identifier id = info.getSelector().getPrimaryId();
+ if (!passesFilter(id) || !shouldIncludeInModified(info)) {
+ continue;
+ }
+ mProgramInfoMap.put(id, info);
+ modified.add(info);
+ }
+ for (ProgramSelector.Identifier id : chunk.getRemoved()) {
+ if (mProgramInfoMap.containsKey(id)) {
+ mProgramInfoMap.remove(id);
+ removed.add(id);
+ }
+ }
+ if (modified.isEmpty() && removed.isEmpty() && mComplete == chunk.isComplete()
+ && !chunk.isPurge()) {
+ return null;
+ }
+ mComplete = chunk.isComplete();
+ return buildChunks(chunk.isPurge(), mComplete, modified, maxNumModifiedPerChunk, removed,
+ maxNumRemovedPerChunk);
+ }
+
+ private boolean passesFilter(ProgramSelector.Identifier id) {
+ if (mFilter == null) {
+ return true;
+ }
+ if (!mFilter.getIdentifierTypes().isEmpty()
+ && !mFilter.getIdentifierTypes().contains(id.getType())) {
+ return false;
+ }
+ if (!mFilter.getIdentifiers().isEmpty() && !mFilter.getIdentifiers().contains(id)) {
+ return false;
+ }
+ return mFilter.areCategoriesIncluded() || !id.isCategoryType();
+ }
+
+ private boolean shouldIncludeInModified(RadioManager.ProgramInfo newInfo) {
+ RadioManager.ProgramInfo oldInfo = mProgramInfoMap.get(
+ newInfo.getSelector().getPrimaryId());
+ if (oldInfo == null) {
+ return true;
+ }
+ if (mFilter != null && mFilter.areModificationsExcluded()) {
+ return false;
+ }
+ return !oldInfo.equals(newInfo);
+ }
+
+ private static int roundUpFraction(int numerator, int denominator) {
+ return (numerator / denominator) + (numerator % denominator > 0 ? 1 : 0);
+ }
+
+ private static List<ProgramList.Chunk> buildChunks(boolean purge, boolean complete,
+ @Nullable Collection<RadioManager.ProgramInfo> modified, int maxNumModifiedPerChunk,
+ @Nullable Collection<ProgramSelector.Identifier> removed, int maxNumRemovedPerChunk) {
+ // Communication protocol requires that if purge is set, removed is empty.
+ if (purge) {
+ removed = null;
+ }
+
+ // Determine number of chunks we need to send.
+ int numChunks = purge ? 1 : 0;
+ if (modified != null) {
+ numChunks = Math.max(numChunks,
+ roundUpFraction(modified.size(), maxNumModifiedPerChunk));
+ }
+ if (removed != null) {
+ numChunks = Math.max(numChunks, roundUpFraction(removed.size(), maxNumRemovedPerChunk));
+ }
+ if (numChunks == 0) {
+ return new ArrayList<>();
+ }
+
+ // Try to make similarly-sized chunks by evenly distributing elements from modified and
+ // removed among them.
+ int modifiedPerChunk = 0;
+ int removedPerChunk = 0;
+ Iterator<RadioManager.ProgramInfo> modifiedIter = null;
+ Iterator<ProgramSelector.Identifier> removedIter = null;
+ if (modified != null) {
+ modifiedPerChunk = roundUpFraction(modified.size(), numChunks);
+ modifiedIter = modified.iterator();
+ }
+ if (removed != null) {
+ removedPerChunk = roundUpFraction(removed.size(), numChunks);
+ removedIter = removed.iterator();
+ }
+ List<ProgramList.Chunk> chunks = new ArrayList<>(numChunks);
+ for (int i = 0; i < numChunks; i++) {
+ ArraySet<RadioManager.ProgramInfo> modifiedChunk = new ArraySet<>();
+ ArraySet<ProgramSelector.Identifier> removedChunk = new ArraySet<>();
+ if (modifiedIter != null) {
+ for (int j = 0; j < modifiedPerChunk && modifiedIter.hasNext(); j++) {
+ modifiedChunk.add(modifiedIter.next());
+ }
+ }
+ if (removedIter != null) {
+ for (int j = 0; j < removedPerChunk && removedIter.hasNext(); j++) {
+ removedChunk.add(removedIter.next());
+ }
+ }
+ chunks.add(new ProgramList.Chunk(purge && i == 0, complete && (i == numChunks - 1),
+ modifiedChunk, removedChunk));
+ }
+ return chunks;
+ }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioLogger.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioLogger.java
new file mode 100644
index 0000000..cca351b
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioLogger.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
+import android.util.Log;
+
+import com.android.server.utils.Slogf;
+
+/**
+ * Event logger to log and dump events of radio module and tuner session
+ * for AIDL broadcast radio HAL
+ */
+final class RadioLogger {
+ private final String mTag;
+ private final boolean mDebug;
+ private final LocalLog mEventLogger;
+
+ RadioLogger(String tag, int loggerQueueSize) {
+ mTag = tag;
+ mDebug = Log.isLoggable(mTag, Log.DEBUG);
+ mEventLogger = new LocalLog(loggerQueueSize);
+ }
+
+ void logRadioEvent(String logFormat, Object... args) {
+ String log = TextUtils.formatSimple(logFormat, args);
+ mEventLogger.log(log);
+ if (mDebug) {
+ Slogf.d(mTag, logFormat, args);
+ }
+ }
+
+ void dump(IndentingPrintWriter pw) {
+ mEventLogger.dump(pw);
+ }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
new file mode 100644
index 0000000..c6dc431
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -0,0 +1,523 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.hardware.broadcastradio.AmFmRegionConfig;
+import android.hardware.broadcastradio.Announcement;
+import android.hardware.broadcastradio.DabTableEntry;
+import android.hardware.broadcastradio.IAnnouncementListener;
+import android.hardware.broadcastradio.IBroadcastRadio;
+import android.hardware.broadcastradio.ICloseHandle;
+import android.hardware.broadcastradio.ITunerCallback;
+import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.broadcastradio.ProgramListChunk;
+import android.hardware.broadcastradio.ProgramSelector;
+import android.hardware.broadcastradio.VendorKeyValue;
+import android.hardware.radio.RadioManager;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+final class RadioModule {
+ private static final String TAG = "BcRadioAidlSrv.module";
+ private static final int RADIO_EVENT_LOGGER_QUEUE_SIZE = 25;
+
+ private final IBroadcastRadio mService;
+ public final RadioManager.ModuleProperties mProperties;
+
+ private final Object mLock;
+ private final Handler mHandler;
+ private final RadioLogger mLogger;
+
+ /**
+ * Tracks antenna state reported by HAL (if any).
+ */
+ @GuardedBy("mLock")
+ private Boolean mAntennaConnected;
+
+ @GuardedBy("mLock")
+ private RadioManager.ProgramInfo mCurrentProgramInfo;
+
+ @GuardedBy("mLock")
+ private final ProgramInfoCache mProgramInfoCache = new ProgramInfoCache(null);
+
+ @GuardedBy("mLock")
+ private android.hardware.radio.ProgramList.Filter mUnionOfAidlProgramFilters;
+
+ /**
+ * Set of active AIDL tuner sessions created through openSession().
+ */
+ @GuardedBy("mLock")
+ private final ArraySet<TunerSession> mAidlTunerSessions = new ArraySet<>();
+
+ /**
+ * Callback registered with the HAL to relay callbacks to AIDL clients.
+ */
+ private final ITunerCallback mHalTunerCallback = new ITunerCallback.Stub() {
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ return this.HASH;
+ }
+
+ public void onTuneFailed(int result, ProgramSelector programSelector) {
+ fireLater(() -> {
+ synchronized (mLock) {
+ android.hardware.radio.ProgramSelector csel =
+ ConversionUtils.programSelectorFromHalProgramSelector(programSelector);
+ fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel));
+ }
+ });
+ }
+
+ @Override
+ public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) {
+ fireLater(() -> {
+ synchronized (mLock) {
+ mCurrentProgramInfo =
+ ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo);
+ RadioManager.ProgramInfo currentProgramInfo = mCurrentProgramInfo;
+ fanoutAidlCallbackLocked(cb -> {
+ cb.onCurrentProgramInfoChanged(currentProgramInfo);
+ });
+ }
+ });
+ }
+
+ @Override
+ public void onProgramListUpdated(ProgramListChunk programListChunk) {
+ fireLater(() -> {
+ synchronized (mLock) {
+ android.hardware.radio.ProgramList.Chunk chunk =
+ ConversionUtils.chunkFromHalProgramListChunk(programListChunk);
+ mProgramInfoCache.filterAndApplyChunk(chunk);
+
+ for (int i = 0; i < mAidlTunerSessions.size(); i++) {
+ mAidlTunerSessions.valueAt(i).onMergedProgramListUpdateFromHal(chunk);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onAntennaStateChange(boolean connected) {
+ fireLater(() -> {
+ synchronized (mLock) {
+ mAntennaConnected = connected;
+ fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected));
+ }
+ });
+ }
+
+ @Override
+ public void onConfigFlagUpdated(int flag, boolean value) {
+ fireLater(() -> {
+ // TODO(b/243853343): implement config flag update method in
+ // android.hardware.radio.ITunerCallback
+ });
+ }
+
+ @Override
+ public void onParametersUpdated(VendorKeyValue[] parameters) {
+ fireLater(() -> {
+ synchronized (mLock) {
+ Map<String, String> cparam =
+ ConversionUtils.vendorInfoFromHalVendorKeyValues(parameters);
+ fanoutAidlCallbackLocked(cb -> cb.onParametersUpdated(cparam));
+ }
+ });
+ }
+ };
+
+ @VisibleForTesting
+ RadioModule(IBroadcastRadio service,
+ RadioManager.ModuleProperties properties, Object lock) {
+ mProperties = Objects.requireNonNull(properties, "properties cannot be null");
+ mService = Objects.requireNonNull(service, "service cannot be null");
+ mLock = Objects.requireNonNull(lock, "lock cannot be null");
+ mHandler = new Handler(Looper.getMainLooper());
+ mLogger = new RadioLogger(TAG, RADIO_EVENT_LOGGER_QUEUE_SIZE);
+ }
+
+ @Nullable
+ public static RadioModule tryLoadingModule(int moduleId, String moduleName,
+ IBinder serviceBinder, Object lock) {
+ try {
+ Slogf.i(TAG, "Try loading module for module id = %d, module name = %s",
+ moduleId, moduleName);
+ IBroadcastRadio service = IBroadcastRadio.Stub
+ .asInterface(serviceBinder);
+ if (service == null) {
+ Slogf.w(TAG, "Module %s is null", moduleName);
+ return null;
+ }
+
+ AmFmRegionConfig amfmConfig;
+ try {
+ amfmConfig = service.getAmFmRegionConfig(/* full= */ false);
+ } catch (RuntimeException ex) {
+ Slogf.i(TAG, "Module %s does not has AMFM config", moduleName);
+ amfmConfig = null;
+ }
+
+ DabTableEntry[] dabConfig;
+ try {
+ dabConfig = service.getDabRegionConfig();
+ } catch (RuntimeException ex) {
+ Slogf.i(TAG, "Module %s does not has DAB config", moduleName);
+ dabConfig = null;
+ }
+
+ RadioManager.ModuleProperties prop = ConversionUtils.propertiesFromHalProperties(
+ moduleId, moduleName, service.getProperties(), amfmConfig, dabConfig);
+
+ return new RadioModule(service, prop, lock);
+ } catch (RemoteException ex) {
+ Slogf.e(TAG, ex, "Failed to load module %s", moduleName);
+ return null;
+ }
+ }
+
+ public IBroadcastRadio getService() {
+ return mService;
+ }
+
+ void setInternalHalCallback() throws RemoteException {
+ synchronized (mLock) {
+ mService.setTunerCallback(mHalTunerCallback);
+ }
+ }
+
+ public TunerSession openSession(android.hardware.radio.ITunerCallback userCb)
+ throws RemoteException {
+ mLogger.logRadioEvent("Open TunerSession");
+ TunerSession tunerSession;
+ Boolean antennaConnected;
+ RadioManager.ProgramInfo currentProgramInfo;
+ synchronized (mLock) {
+ tunerSession = new TunerSession(this, mService, userCb, mLock);
+ mAidlTunerSessions.add(tunerSession);
+ antennaConnected = mAntennaConnected;
+ currentProgramInfo = mCurrentProgramInfo;
+ }
+ // Propagate state to new client.
+ // Note: These callbacks are invoked while holding mLock to prevent race conditions
+ // with new callbacks from the HAL.
+ if (antennaConnected != null) {
+ userCb.onAntennaState(antennaConnected);
+ }
+ if (currentProgramInfo != null) {
+ userCb.onCurrentProgramInfoChanged(currentProgramInfo);
+ }
+
+ return tunerSession;
+ }
+
+ public void closeSessions(int error) {
+ mLogger.logRadioEvent("Close TunerSessions %d", error);
+ // TunerSession.close() must be called without mAidlTunerSessions locked because
+ // it can call onTunerSessionClosed(). Therefore, the contents of mAidlTunerSessions
+ // are copied into a local array here.
+ TunerSession[] tunerSessions;
+ synchronized (mLock) {
+ tunerSessions = new TunerSession[mAidlTunerSessions.size()];
+ mAidlTunerSessions.toArray(tunerSessions);
+ mAidlTunerSessions.clear();
+ }
+
+ for (TunerSession tunerSession : tunerSessions) {
+ try {
+ tunerSession.close(error);
+ } catch (Exception e) {
+ Slogf.e(TAG, "Failed to close TunerSession %s: %s", tunerSession, e);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private android.hardware.radio.ProgramList.Filter
+ buildUnionOfTunerSessionFiltersLocked() {
+ Set<Integer> idTypes = null;
+ Set<android.hardware.radio.ProgramSelector.Identifier> ids = null;
+ boolean includeCategories = false;
+ boolean excludeModifications = true;
+
+ for (int i = 0; i < mAidlTunerSessions.size(); i++) {
+ android.hardware.radio.ProgramList.Filter filter =
+ mAidlTunerSessions.valueAt(i).getProgramListFilter();
+ if (filter == null) {
+ continue;
+ }
+
+ if (idTypes == null) {
+ idTypes = new ArraySet<>(filter.getIdentifierTypes());
+ ids = new ArraySet<>(filter.getIdentifiers());
+ includeCategories = filter.areCategoriesIncluded();
+ excludeModifications = filter.areModificationsExcluded();
+ continue;
+ }
+ if (!idTypes.isEmpty()) {
+ if (filter.getIdentifierTypes().isEmpty()) {
+ idTypes.clear();
+ } else {
+ idTypes.addAll(filter.getIdentifierTypes());
+ }
+ }
+
+ if (!ids.isEmpty()) {
+ if (filter.getIdentifiers().isEmpty()) {
+ ids.clear();
+ } else {
+ ids.addAll(filter.getIdentifiers());
+ }
+ }
+
+ includeCategories |= filter.areCategoriesIncluded();
+ excludeModifications &= filter.areModificationsExcluded();
+ }
+
+ return idTypes == null ? null : new android.hardware.radio.ProgramList.Filter(idTypes, ids,
+ includeCategories, excludeModifications);
+ }
+
+ void onTunerSessionProgramListFilterChanged(@Nullable TunerSession session) {
+ synchronized (mLock) {
+ onTunerSessionProgramListFilterChangedLocked(session);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onTunerSessionProgramListFilterChangedLocked(@Nullable TunerSession session) {
+ android.hardware.radio.ProgramList.Filter newFilter =
+ buildUnionOfTunerSessionFiltersLocked();
+ if (newFilter == null) {
+ // If there are no AIDL clients remaining, we can stop updates from the HAL as well.
+ if (mUnionOfAidlProgramFilters == null) {
+ return;
+ }
+ mUnionOfAidlProgramFilters = null;
+ try {
+ mService.stopProgramListUpdates();
+ } catch (RemoteException ex) {
+ Slogf.e(TAG, ex, "mHalTunerSession.stopProgramListUpdates() failed");
+ }
+ return;
+ }
+
+ synchronized (mLock) {
+ // If the HAL filter doesn't change, we can immediately send an update to the AIDL
+ // client.
+ if (newFilter.equals(mUnionOfAidlProgramFilters)) {
+ if (session != null) {
+ session.updateProgramInfoFromHalCache(mProgramInfoCache);
+ }
+ return;
+ }
+
+ // Otherwise, update the HAL's filter, and AIDL clients will be updated when
+ // mHalTunerCallback.onProgramListUpdated() is called.
+ mUnionOfAidlProgramFilters = newFilter;
+ try {
+ mService.startProgramListUpdates(
+ ConversionUtils.filterToHalProgramFilter(newFilter));
+ } catch (RuntimeException ex) {
+ throw ConversionUtils.throwOnError(ex, /* action= */ "Start Program ListUpdates");
+ } catch (RemoteException ex) {
+ Slogf.e(TAG, ex, "mHalTunerSession.startProgramListUpdates() failed");
+ }
+ }
+ }
+
+ void onTunerSessionClosed(TunerSession tunerSession) {
+ synchronized (mLock) {
+ onTunerSessionsClosedLocked(tunerSession);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onTunerSessionsClosedLocked(TunerSession... tunerSessions) {
+ for (TunerSession tunerSession : tunerSessions) {
+ mAidlTunerSessions.remove(tunerSession);
+ }
+ onTunerSessionProgramListFilterChanged(null);
+ }
+
+ // add to mHandler queue
+ private void fireLater(Runnable r) {
+ mHandler.post(() -> r.run());
+ }
+
+ interface AidlCallbackRunnable {
+ void run(android.hardware.radio.ITunerCallback callback) throws RemoteException;
+ }
+
+ // Invokes runnable with each TunerSession currently open.
+ void fanoutAidlCallback(AidlCallbackRunnable runnable) {
+ fireLater(() -> {
+ synchronized (mLock) {
+ fanoutAidlCallbackLocked(runnable);
+ }
+ });
+ }
+
+ @GuardedBy("mLock")
+ private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) {
+ List<TunerSession> deadSessions = null;
+ for (int i = 0; i < mAidlTunerSessions.size(); i++) {
+ try {
+ runnable.run(mAidlTunerSessions.valueAt(i).mCallback);
+ } catch (DeadObjectException ex) {
+ // The other side died without calling close(), so just purge it from our records.
+ Slogf.e(TAG, "Removing dead TunerSession");
+ if (deadSessions == null) {
+ deadSessions = new ArrayList<>();
+ }
+ deadSessions.add(mAidlTunerSessions.valueAt(i));
+ } catch (RemoteException ex) {
+ Slogf.e(TAG, ex, "Failed to invoke ITunerCallback");
+ }
+ }
+ if (deadSessions != null) {
+ onTunerSessionsClosedLocked(deadSessions.toArray(
+ new TunerSession[deadSessions.size()]));
+ }
+ }
+
+ public android.hardware.radio.ICloseHandle addAnnouncementListener(
+ android.hardware.radio.IAnnouncementListener listener,
+ int[] enabledTypes) throws RemoteException {
+ mLogger.logRadioEvent("Add AnnouncementListener");
+ byte[] enabledList = new byte[enabledTypes.length];
+ for (int index = 0; index < enabledList.length; index++) {
+ enabledList[index] = (byte) enabledTypes[index];
+ }
+
+ final ICloseHandle[] hwCloseHandle = {null};
+ IAnnouncementListener hwListener = new IAnnouncementListener.Stub() {
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+
+ public String getInterfaceHash() {
+ return this.HASH;
+ }
+
+ public void onListUpdated(Announcement[] hwAnnouncements)
+ throws RemoteException {
+ List<android.hardware.radio.Announcement> announcements =
+ new ArrayList<>(hwAnnouncements.length);
+ for (int i = 0; i < hwAnnouncements.length; i++) {
+ announcements.add(
+ ConversionUtils.announcementFromHalAnnouncement(hwAnnouncements[i]));
+ }
+ listener.onListUpdated(announcements);
+ }
+ };
+
+ synchronized (mLock) {
+ try {
+ hwCloseHandle[0] = mService.registerAnnouncementListener(hwListener, enabledList);
+ } catch (RuntimeException ex) {
+ throw ConversionUtils.throwOnError(ex, /* action= */ "AnnouncementListener");
+ }
+ }
+
+ return new android.hardware.radio.ICloseHandle.Stub() {
+ public void close() {
+ try {
+ hwCloseHandle[0].close();
+ } catch (RemoteException ex) {
+ Slogf.e(TAG, ex, "Failed closing announcement listener");
+ }
+ hwCloseHandle[0] = null;
+ }
+ };
+ }
+
+ Bitmap getImage(int id) {
+ mLogger.logRadioEvent("Get image for id = %d", id);
+ if (id == 0) throw new IllegalArgumentException("Image ID is missing");
+
+ byte[] rawImage;
+ synchronized (mLock) {
+ try {
+ rawImage = mService.getImage(id);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ if (rawImage == null || rawImage.length == 0) return null;
+
+ return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
+ }
+
+ void dumpInfo(IndentingPrintWriter pw) {
+ pw.printf("RadioModule\n");
+
+ pw.increaseIndent();
+ synchronized (mLock) {
+ pw.printf("BroadcastRadioServiceImpl: %s\n", mService);
+ pw.printf("Properties: %s\n", mProperties);
+ pw.printf("Antenna state: ");
+ if (mAntennaConnected == null) {
+ pw.printf("undetermined\n");
+ } else {
+ pw.printf("%s\n", mAntennaConnected ? "connected" : "not connected");
+ }
+ pw.printf("current ProgramInfo: %s\n", mCurrentProgramInfo);
+ pw.printf("ProgramInfoCache: %s\n", mProgramInfoCache);
+ pw.printf("Union of AIDL ProgramFilters: %s\n", mUnionOfAidlProgramFilters);
+ pw.printf("AIDL TunerSessions [%d]:\n", mAidlTunerSessions.size());
+
+ pw.increaseIndent();
+ for (int i = 0; i < mAidlTunerSessions.size(); i++) {
+ mAidlTunerSessions.valueAt(i).dumpInfo(pw);
+ }
+ pw.decreaseIndent();
+ }
+ pw.printf("Radio module events:\n");
+
+ pw.increaseIndent();
+ mLogger.dump(pw);
+ pw.decreaseIndent();
+
+ pw.decreaseIndent();
+ }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
new file mode 100644
index 0000000..7c26a87
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.hardware.broadcastradio.ConfigFlag;
+import android.hardware.broadcastradio.IBroadcastRadio;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.utils.Slogf;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+final class TunerSession extends ITuner.Stub {
+ private static final String TAG = "BcRadioAidlSrv.session";
+ private static final int TUNER_EVENT_LOGGER_QUEUE_SIZE = 25;
+
+ private final Object mLock;
+
+ private final RadioLogger mLogger;
+ private final RadioModule mModule;
+ final android.hardware.radio.ITunerCallback mCallback;
+ private final IBroadcastRadio mService;
+
+ @GuardedBy("mLock")
+ private boolean mIsClosed;
+ @GuardedBy("mLock")
+ private boolean mIsMuted;
+ @GuardedBy("mLock")
+ private ProgramInfoCache mProgramInfoCache;
+
+ // necessary only for older APIs compatibility
+ @GuardedBy("mLock")
+ private RadioManager.BandConfig mPlaceHolderConfig;
+
+ TunerSession(RadioModule radioModule, IBroadcastRadio service,
+ android.hardware.radio.ITunerCallback callback,
+ Object lock) {
+ mModule = Objects.requireNonNull(radioModule, "radioModule cannot be null");
+ mService = Objects.requireNonNull(service, "service cannot be null");
+ mCallback = Objects.requireNonNull(callback, "callback cannot be null");
+ mLock = Objects.requireNonNull(lock, "lock cannot be null");
+ mLogger = new RadioLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE);
+ }
+
+ @Override
+ public void close() {
+ mLogger.logRadioEvent("Close tuner session");
+ close(null);
+ }
+
+ /**
+ * Closes the TunerSession. If error is non-null, the client's onError() callback is invoked
+ * first with the specified error, see {@link
+ * android.hardware.radio.RadioTuner.Callback#onError}.
+ *
+ * @param error Error to send to client before session is closed. If null, there is no error
+ * when closing the session.
+ */
+ public void close(@Nullable Integer error) {
+ if (error == null) {
+ mLogger.logRadioEvent("Close tuner session on error null");
+ } else {
+ mLogger.logRadioEvent("Close tuner session on error %d", error);
+ }
+ synchronized (mLock) {
+ if (mIsClosed) return;
+ if (error != null) {
+ try {
+ mCallback.onError(error);
+ } catch (RemoteException ex) {
+ Slogf.w(TAG, ex, "mCallback.onError(%s) failed", error);
+ }
+ }
+ mIsClosed = true;
+ mModule.onTunerSessionClosed(this);
+ }
+ }
+
+ @Override
+ public boolean isClosed() {
+ synchronized (mLock) {
+ return mIsClosed;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void checkNotClosedLocked() {
+ if (mIsClosed) {
+ throw new IllegalStateException("Tuner is closed, no further operations are allowed");
+ }
+ }
+
+ @Override
+ public void setConfiguration(RadioManager.BandConfig config) {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ mPlaceHolderConfig = Objects.requireNonNull(config, "config cannot be null");
+ }
+ Slogf.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL AIDL");
+ mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config));
+ }
+
+ @Override
+ public RadioManager.BandConfig getConfiguration() {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ return mPlaceHolderConfig;
+ }
+ }
+
+ @Override
+ public void setMuted(boolean mute) {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ if (mIsMuted == mute) return;
+ mIsMuted = mute;
+ }
+ Slogf.w(TAG, "Mute %b via RadioService is not implemented - please handle it via app",
+ mute);
+ }
+
+ @Override
+ public boolean isMuted() {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ return mIsMuted;
+ }
+ }
+
+ @Override
+ public void step(boolean directionDown, boolean skipSubChannel) throws RemoteException {
+ mLogger.logRadioEvent("Step with direction %s, skipSubChannel? %s",
+ directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ try {
+ mService.step(!directionDown);
+ } catch (RuntimeException ex) {
+ throw ConversionUtils.throwOnError(ex, /* action= */ "step");
+ }
+ }
+ }
+
+ @Override
+ public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException {
+ mLogger.logRadioEvent("Scan with direction %s, skipSubChannel? %s",
+ directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ try {
+ mService.seek(!directionDown, skipSubChannel);
+ } catch (RuntimeException ex) {
+ throw ConversionUtils.throwOnError(ex, /* action= */ "seek");
+ }
+ }
+ }
+
+ @Override
+ public void tune(ProgramSelector selector) throws RemoteException {
+ mLogger.logRadioEvent("Tune with selector %s", selector);
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ try {
+ mService.tune(ConversionUtils.programSelectorToHalProgramSelector(selector));
+ } catch (RuntimeException ex) {
+ throw ConversionUtils.throwOnError(ex, /* action= */ "tune");
+ }
+ }
+ }
+
+ @Override
+ public void cancel() {
+ Slogf.i(TAG, "Cancel");
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ try {
+ mService.cancel();
+ } catch (RemoteException ex) {
+ Slogf.e(TAG, "Failed to cancel tuner session");
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ @Override
+ public void cancelAnnouncement() {
+ // TODO(b/244485175): deperacte cancelAnnouncement
+ Slogf.i(TAG, "Announcements control doesn't involve cancelling at the HAL level in AIDL");
+ }
+
+ @Override
+ public Bitmap getImage(int id) {
+ mLogger.logRadioEvent("Get image for %d", id);
+ return mModule.getImage(id);
+ }
+
+ @Override
+ public boolean startBackgroundScan() {
+ Slogf.i(TAG, "Explicit background scan trigger is not supported with HAL AIDL");
+ mModule.fanoutAidlCallback(ITunerCallback::onBackgroundScanComplete);
+ return true;
+ }
+
+ @Override
+ public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException {
+ mLogger.logRadioEvent("Start programList updates %s", filter);
+ // If the AIDL client provides a null filter, it wants all updates, so use the most broad
+ // filter.
+ if (filter == null) {
+ filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+ /* includeCategories= */ true,
+ /* excludeModifications= */ false);
+ }
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ mProgramInfoCache = new ProgramInfoCache(filter);
+ }
+ // Note: RadioModule.onTunerSessionProgramListFilterChanged() must be called without mLock
+ // held since it can call getProgramListFilter() and onHalProgramInfoUpdated().
+ mModule.onTunerSessionProgramListFilterChanged(this);
+ }
+
+ ProgramList.Filter getProgramListFilter() {
+ synchronized (mLock) {
+ return mProgramInfoCache == null ? null : mProgramInfoCache.getFilter();
+ }
+ }
+
+ void onMergedProgramListUpdateFromHal(ProgramList.Chunk mergedChunk) {
+ List<ProgramList.Chunk> clientUpdateChunks;
+ synchronized (mLock) {
+ if (mProgramInfoCache == null) {
+ return;
+ }
+ clientUpdateChunks = mProgramInfoCache.filterAndApplyChunk(mergedChunk);
+ }
+ dispatchClientUpdateChunks(clientUpdateChunks);
+ }
+
+ void updateProgramInfoFromHalCache(ProgramInfoCache halCache) {
+ List<ProgramList.Chunk> clientUpdateChunks;
+ synchronized (mLock) {
+ if (mProgramInfoCache == null) {
+ return;
+ }
+ clientUpdateChunks = mProgramInfoCache.filterAndUpdateFromInternal(
+ halCache, /* purge = */ true);
+ }
+ dispatchClientUpdateChunks(clientUpdateChunks);
+ }
+
+ private void dispatchClientUpdateChunks(@Nullable List<ProgramList.Chunk> chunks) {
+ if (chunks == null) {
+ return;
+ }
+ for (int i = 0; i < chunks.size(); i++) {
+ try {
+ mCallback.onProgramListUpdated(chunks.get(i));
+ } catch (RemoteException ex) {
+ Slogf.w(TAG, ex, "mCallback.onProgramListUpdated() failed");
+ }
+ }
+ }
+
+ @Override
+ public void stopProgramListUpdates() throws RemoteException {
+ mLogger.logRadioEvent("Stop programList updates");
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ mProgramInfoCache = null;
+ }
+ // Note: RadioModule.onTunerSessionProgramListFilterChanged() must be called without mLock
+ // held since it can call getProgramListFilter() and onHalProgramInfoUpdated().
+ mModule.onTunerSessionProgramListFilterChanged(this);
+ }
+
+ @Override
+ public boolean isConfigFlagSupported(int flag) {
+ try {
+ isConfigFlagSet(flag);
+ return true;
+ } catch (IllegalStateException | UnsupportedOperationException ex) {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isConfigFlagSet(int flag) {
+ mLogger.logRadioEvent("is ConfigFlag %s set? ", ConfigFlag.$.toString(flag));
+ synchronized (mLock) {
+ checkNotClosedLocked();
+
+ try {
+ return mService.isConfigFlagSet(flag);
+ } catch (RuntimeException ex) {
+ throw ConversionUtils.throwOnError(ex, /* action= */ "isConfigFlagSet");
+ } catch (RemoteException ex) {
+ throw new RuntimeException("Failed to check flag " + ConfigFlag.$.toString(flag),
+ ex);
+ }
+ }
+ }
+
+ @Override
+ public void setConfigFlag(int flag, boolean value) throws RemoteException {
+ mLogger.logRadioEvent("set ConfigFlag %s to %b ",
+ ConfigFlag.$.toString(flag), value);
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ try {
+ mService.setConfigFlag(flag, value);
+ } catch (RuntimeException ex) {
+ throw ConversionUtils.throwOnError(ex, /* action= */ "setConfigFlag");
+ }
+ }
+ }
+
+ @Override
+ public Map<String, String> setParameters(Map<String, String> parameters) {
+ mLogger.logRadioEvent("Set parameters ");
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ try {
+ return ConversionUtils.vendorInfoFromHalVendorKeyValues(mService.setParameters(
+ ConversionUtils.vendorInfoToHalVendorKeyValues(parameters)));
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ @Override
+ public Map<String, String> getParameters(List<String> keys) {
+ mLogger.logRadioEvent("Get parameters ");
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ try {
+ return ConversionUtils.vendorInfoFromHalVendorKeyValues(
+ mService.getParameters(keys.toArray(new String[0])));
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ void dumpInfo(IndentingPrintWriter pw) {
+ pw.printf("TunerSession\n");
+
+ pw.increaseIndent();
+ synchronized (mLock) {
+ pw.printf("Is session closed? %s\n", mIsClosed ? "Yes" : "No");
+ pw.printf("Is muted? %s\n", mIsMuted ? "Yes" : "No");
+ pw.printf("ProgramInfoCache: %s\n", mProgramInfoCache);
+ pw.printf("Config: %s\n", mPlaceHolderConfig);
+ }
+ pw.printf("Tuner session events:\n");
+
+ pw.increaseIndent();
+ mLogger.dump(pw);
+ pw.decreaseIndent();
+
+ pw.decreaseIndent();
+ }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/Utils.java b/services/core/java/com/android/server/broadcastradio/aidl/Utils.java
new file mode 100644
index 0000000..b6bea5b
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/aidl/Utils.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+final class Utils {
+
+ private Utils() {
+ throw new UnsupportedOperationException("Utils class is noninstantiable");
+ }
+
+ public enum FrequencyBand {
+ UNKNOWN,
+ FM,
+ AM_LW,
+ AM_MW,
+ AM_SW,
+ }
+
+ static FrequencyBand getBand(int freq) {
+ // keep in sync with hardware/interfaces/broadcastradio/common/utilsaidl/Utils.cpp
+ if (freq < 30) return FrequencyBand.UNKNOWN;
+ if (freq < 500) return FrequencyBand.AM_LW;
+ if (freq < 1705) return FrequencyBand.AM_MW;
+ if (freq < 30000) return FrequencyBand.AM_SW;
+ if (freq < 60000) return FrequencyBand.UNKNOWN;
+ if (freq < 110000) return FrequencyBand.FM;
+ return FrequencyBand.UNKNOWN;
+ }
+
+}
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index 2c6257f..5c679b8 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -2083,7 +2083,7 @@
try (FileInputStream in = mStatusFile.openRead()) {
readStatusInfoLocked(in);
}
- } catch (IOException e) {
+ } catch (Exception e) {
Slog.e(TAG, "Unable to read status info file.", e);
}
}
@@ -2483,7 +2483,7 @@
try (FileInputStream in = mStatisticsFile.openRead()) {
readDayStatsLocked(in);
}
- } catch (IOException e) {
+ } catch (Exception e) {
Slog.e(TAG, "Unable to read day stats file.", e);
}
}
diff --git a/services/core/java/com/android/server/display/WifiDisplayController.java b/services/core/java/com/android/server/display/WifiDisplayController.java
index 5b204ad..f6d06aa 100644
--- a/services/core/java/com/android/server/display/WifiDisplayController.java
+++ b/services/core/java/com/android/server/display/WifiDisplayController.java
@@ -885,7 +885,7 @@
}
});
}
- } else {
+ } else if (!networkInfo.isConnectedOrConnecting()) {
mConnectedDeviceGroupInfo = null;
// Disconnect if we lost the network while connecting or connected to a display.
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index e7f6e67..349b94b 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -425,18 +425,15 @@
}
private void onPackageReset(String packageName) {
- // invoked when a package is "force quit" - move off the main thread
- FgThread.getExecutor().execute(
- () ->
- updateRegistrations(
- registration -> {
- if (registration.getIdentity().getPackageName().equals(
- packageName)) {
- registration.remove();
- }
+ updateRegistrations(
+ registration -> {
+ if (registration.getIdentity().getPackageName().equals(
+ packageName)) {
+ registration.remove();
+ }
- return false;
- }));
+ return false;
+ });
}
private boolean isResetableForPackage(String packageName) {
diff --git a/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java b/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java
index 91b0212..b0fe55d 100644
--- a/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java
@@ -27,6 +27,7 @@
import android.net.Uri;
import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
/** Listens to appropriate broadcasts for queries and resets. */
public class SystemPackageResetHelper extends PackageResetHelper {
@@ -120,7 +121,9 @@
context.getPackageManager().getApplicationInfo(packageName,
PackageManager.ApplicationInfoFlags.of(0));
if (!appInfo.enabled) {
- notifyPackageReset(packageName);
+ // move off main thread
+ FgThread.getExecutor().execute(
+ () -> notifyPackageReset(packageName));
}
} catch (PackageManager.NameNotFoundException e) {
return;
@@ -130,7 +133,8 @@
case Intent.ACTION_PACKAGE_REMOVED:
// fall through
case Intent.ACTION_PACKAGE_RESTARTED:
- notifyPackageReset(packageName);
+ // move off main thread
+ FgThread.getExecutor().execute(() -> notifyPackageReset(packageName));
break;
default:
break;
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index bd75251..338a995 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -2409,18 +2409,15 @@
}
private void onPackageReset(String packageName) {
- // invoked when a package is "force quit" - move off the main thread
- FgThread.getExecutor().execute(
- () ->
- updateRegistrations(
- registration -> {
- if (registration.getIdentity().getPackageName().equals(
- packageName)) {
- registration.remove();
- }
+ updateRegistrations(
+ registration -> {
+ if (registration.getIdentity().getPackageName().equals(
+ packageName)) {
+ registration.remove();
+ }
- return false;
- }));
+ return false;
+ });
}
private boolean isResetableForPackage(String packageName) {
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 3211ca1..50bb051 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2161,7 +2161,7 @@
}
public final String resolveExternalPackageName(AndroidPackage pkg) {
- if (pkg.getStaticSharedLibName() != null) {
+ if (pkg.getStaticSharedLibraryName() != null) {
return pkg.getManifestPackageName();
}
return pkg.getPackageName();
@@ -2378,7 +2378,7 @@
}
final SharedLibraryInfo libraryInfo = getSharedLibraryInfo(
- ps.getPkg().getStaticSharedLibName(), ps.getPkg().getStaticSharedLibVersion());
+ ps.getPkg().getStaticSharedLibraryName(), ps.getPkg().getStaticSharedLibVersion());
if (libraryInfo == null) {
return false;
}
@@ -2434,7 +2434,7 @@
}
final SharedLibraryInfo libraryInfo = getSharedLibraryInfo(
- ps.getPkg().getSdkLibName(), ps.getPkg().getSdkLibVersionMajor());
+ ps.getPkg().getSdkLibraryName(), ps.getPkg().getSdkLibVersionMajor());
if (libraryInfo == null) {
return false;
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 7242a56..7ff91f82 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -184,11 +184,11 @@
if (pkg != null) {
SharedLibraryInfo libraryInfo = null;
- if (pkg.getStaticSharedLibName() != null) {
- libraryInfo = computer.getSharedLibraryInfo(pkg.getStaticSharedLibName(),
+ if (pkg.getStaticSharedLibraryName() != null) {
+ libraryInfo = computer.getSharedLibraryInfo(pkg.getStaticSharedLibraryName(),
pkg.getStaticSharedLibVersion());
- } else if (pkg.getSdkLibName() != null) {
- libraryInfo = computer.getSharedLibraryInfo(pkg.getSdkLibName(),
+ } else if (pkg.getSdkLibraryName() != null) {
+ libraryInfo = computer.getSharedLibraryInfo(pkg.getSdkLibraryName(),
pkg.getSdkLibVersionMajor());
}
@@ -544,7 +544,7 @@
}
outInfo.mRemovedPackage = ps.getPackageName();
outInfo.mInstallerPackageName = ps.getInstallSource().installerPackageName;
- outInfo.mIsStaticSharedLib = pkg != null && pkg.getStaticSharedLibName() != null;
+ outInfo.mIsStaticSharedLib = pkg != null && pkg.getStaticSharedLibraryName() != null;
outInfo.mRemovedAppId = ps.getAppId();
outInfo.mRemovedUsers = userIds;
outInfo.mBroadcastUsers = userIds;
@@ -895,8 +895,8 @@
final String packageName = ps.getPkg().getPackageName();
// Skip over if system app, static shared library or and SDK library.
if ((ps.getFlags() & ApplicationInfo.FLAG_SYSTEM) != 0
- || !TextUtils.isEmpty(ps.getPkg().getStaticSharedLibName())
- || !TextUtils.isEmpty(ps.getPkg().getSdkLibName())) {
+ || !TextUtils.isEmpty(ps.getPkg().getStaticSharedLibraryName())
+ || !TextUtils.isEmpty(ps.getPkg().getSdkLibraryName())) {
continue;
}
if (DEBUG_CLEAN_APKS) {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 1746d93..81d47a0 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -168,6 +168,7 @@
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.SharedLibraryWrapper;
import com.android.server.pm.pkg.component.ComponentMutateUtils;
import com.android.server.pm.pkg.component.ParsedInstrumentation;
import com.android.server.pm.pkg.component.ParsedPermission;
@@ -351,7 +352,7 @@
}
if (reconciledPkg.mCollectedSharedLibraryInfos != null
- || (oldPkgSetting != null && oldPkgSetting.getUsesLibraryInfos() != null)) {
+ || (oldPkgSetting != null && oldPkgSetting.getUsesLibraries() != null)) {
// Reconcile if the new package or the old package uses shared libraries.
// It is possible that the old package uses shared libraries but the new one doesn't.
mSharedLibraries.executeSharedLibrariesUpdate(pkg, pkgSetting, null, null,
@@ -442,7 +443,7 @@
// Also need to kill any apps that are dependent on the library, except the case of
// installation of new version static shared library.
if (clientLibPkgs != null) {
- if (pkg.getStaticSharedLibName() == null || isReplace) {
+ if (pkg.getStaticSharedLibraryName() == null || isReplace) {
for (int i = 0; i < clientLibPkgs.size(); i++) {
AndroidPackage clientPkg = clientLibPkgs.get(i);
mPm.killApplication(clientPkg.getPackageName(),
@@ -1141,7 +1142,7 @@
if (signatureCheckPs == null && parsedPackage.isSdkLibrary()) {
WatchedLongSparseArray<SharedLibraryInfo> libraryInfos =
mSharedLibraries.getSharedLibraryInfos(
- parsedPackage.getSdkLibName());
+ parsedPackage.getSdkLibraryName());
if (libraryInfos != null && libraryInfos.size() > 0) {
// Any existing version would do.
SharedLibraryInfo libraryInfo = libraryInfos.valueAt(0);
@@ -1578,7 +1579,7 @@
removedInfo.mInstallerPackageName =
ps.getInstallSource().installerPackageName;
removedInfo.mIsStaticSharedLib =
- parsedPackage.getStaticSharedLibName() != null;
+ parsedPackage.getStaticSharedLibraryName() != null;
removedInfo.mIsUpdate = true;
removedInfo.mOrigUsers = installedUsers;
removedInfo.mInstallReasons = new SparseIntArray(installedUsers.length);
@@ -2059,9 +2060,9 @@
// Retrieve the overlays for shared libraries of the package.
if (!ps.getPkgState().getUsesLibraryInfos().isEmpty()) {
- for (SharedLibraryInfo sharedLib : ps.getPkgState().getUsesLibraryInfos()) {
+ for (SharedLibraryWrapper sharedLib : ps.getPkgState().getUsesLibraryInfos()) {
for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
- if (!sharedLib.isDynamic()) {
+ if (sharedLib.getType() != SharedLibraryInfo.TYPE_DYNAMIC) {
// TODO(146804378): Support overlaying static shared libraries
continue;
}
@@ -2684,7 +2685,7 @@
}
// Send installed broadcasts if the package is not a static shared lib.
- if (request.getPkg().getStaticSharedLibName() == null) {
+ if (request.getPkg().getStaticSharedLibraryName() == null) {
mPm.mProcessLoggingHandler.invalidateBaseApkHash(request.getPkg().getBaseApkPath());
// Send added for users that see the package for the first time
@@ -2813,7 +2814,7 @@
// No need to kill consumers if it's installation of new version static shared lib.
final Computer snapshot = mPm.snapshotComputer();
final boolean dontKillApp = !update
- && request.getPkg().getStaticSharedLibName() != null;
+ && request.getPkg().getStaticSharedLibraryName() != null;
for (int i = 0; i < request.getLibraryConsumers().size(); i++) {
AndroidPackage pkg = request.getLibraryConsumers().get(i);
// send broadcast that all consumers of the static shared library have changed
@@ -4297,7 +4298,7 @@
long maxVersionCode = Long.MAX_VALUE;
WatchedLongSparseArray<SharedLibraryInfo> versionedLib =
- mSharedLibraries.getSharedLibraryInfos(pkg.getStaticSharedLibName());
+ mSharedLibraries.getSharedLibraryInfos(pkg.getStaticSharedLibraryName());
if (versionedLib != null) {
final int versionCount = versionedLib.size();
for (int i = 0; i < versionCount; i++) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9481f8a..2c460f8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -210,7 +210,6 @@
import com.android.server.pm.permission.PermissionManagerService;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserState;
import com.android.server.pm.pkg.PackageUserStateInternal;
@@ -223,7 +222,6 @@
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.pm.resolution.ComponentResolver;
import com.android.server.pm.resolution.ComponentResolverApi;
-import com.android.server.pm.snapshot.PackageDataSnapshot;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.verify.domain.DomainVerificationService;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
@@ -5491,19 +5489,19 @@
AndroidPackage pkg = packageState.getPkg();
if (pkg != null) {
// Cannot hide SDK libs as they are controlled by SDK manager.
- if (pkg.getSdkLibName() != null) {
+ if (pkg.getSdkLibraryName() != null) {
Slog.w(TAG, "Cannot hide package: " + packageName
+ " providing SDK library: "
- + pkg.getSdkLibName());
+ + pkg.getSdkLibraryName());
return false;
}
// Cannot hide static shared libs as they are considered
// a part of the using app (emulating static linking). Also
// static libs are installed always on internal storage.
- if (pkg.getStaticSharedLibName() != null) {
+ if (pkg.getStaticSharedLibraryName() != null) {
Slog.w(TAG, "Cannot hide package: " + packageName
+ " providing static shared library: "
- + pkg.getStaticSharedLibName());
+ + pkg.getStaticSharedLibraryName());
return false;
}
}
@@ -5546,17 +5544,17 @@
if (packageState != null && packageState.getPkg() != null) {
AndroidPackage pkg = packageState.getPkg();
// Cannot block uninstall SDK libs as they are controlled by SDK manager.
- if (pkg.getSdkLibName() != null) {
+ if (pkg.getSdkLibraryName() != null) {
Slog.w(PackageManagerService.TAG, "Cannot block uninstall of package: " + packageName
- + " providing SDK library: " + pkg.getSdkLibName());
+ + " providing SDK library: " + pkg.getSdkLibraryName());
return false;
}
// Cannot block uninstall of static shared libs as they are
// considered a part of the using app (emulating static linking).
// Also static libs are installed always on internal storage.
- if (pkg.getStaticSharedLibName() != null) {
+ if (pkg.getStaticSharedLibraryName() != null) {
Slog.w(PackageManagerService.TAG, "Cannot block uninstall of package: " + packageName
- + " providing static shared library: " + pkg.getStaticSharedLibName());
+ + " providing static shared library: " + pkg.getStaticSharedLibraryName());
return false;
}
}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 03f17bd..9050722 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -42,15 +42,17 @@
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DataClass;
import com.android.server.pm.parsing.pkg.AndroidPackageInternal;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.permission.LegacyPermissionDataProvider;
import com.android.server.pm.permission.LegacyPermissionState;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageStateUnserialized;
+import com.android.server.pm.pkg.PackageUserState;
import com.android.server.pm.pkg.PackageUserStateImpl;
import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.SharedLibrary;
+import com.android.server.pm.pkg.SharedLibraryWrapper;
import com.android.server.pm.pkg.SuspendParams;
import com.android.server.utils.SnapshotCache;
import com.android.server.utils.WatchedArraySet;
@@ -1203,13 +1205,13 @@
@NonNull
@Override
- public List<SharedLibraryInfo> getUsesLibraryInfos() {
- return pkgState.getUsesLibraryInfos();
+ public List<SharedLibrary> getUsesLibraries() {
+ return (List<SharedLibrary>) (List<?>) pkgState.getUsesLibraryInfos();
}
@NonNull
public PackageSetting addUsesLibraryInfo(@NonNull SharedLibraryInfo value) {
- pkgState.addUsesLibraryInfo(value);
+ pkgState.addUsesLibraryInfo(new SharedLibraryWrapper(value));
return this;
}
@@ -1326,6 +1328,13 @@
return this;
}
+ @NonNull
+ @Override
+ public PackageUserState getStateForUser(@NonNull UserHandle user) {
+ PackageUserState userState = getUserStates().get(user.getIdentifier());
+ return userState == null ? PackageUserState.DEFAULT : userState;
+ }
+
// Code below generated by codegen v1.0.23.
@@ -1487,10 +1496,10 @@
}
@DataClass.Generated(
- time = 1659546705292L,
+ time = 1662666062860L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
- inputSignatures = "private int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.parsing.pkg.AndroidPackageInternal)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<android.content.pm.SharedLibraryInfo> getUsesLibraryInfos()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+ inputSignatures = "private int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 55d4b36..bbc4fde 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -215,21 +215,21 @@
r = null;
// Any package can hold SDK or static shared libraries.
- if (pkg.getSdkLibName() != null) {
+ if (pkg.getSdkLibraryName() != null) {
if (mSharedLibraries.removeSharedLibrary(
- pkg.getSdkLibName(), pkg.getSdkLibVersionMajor())) {
+ pkg.getSdkLibraryName(), pkg.getSdkLibVersionMajor())) {
if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
r.append(' ');
}
- r.append(pkg.getSdkLibName());
+ r.append(pkg.getSdkLibraryName());
}
}
}
- if (pkg.getStaticSharedLibName() != null) {
- if (mSharedLibraries.removeSharedLibrary(pkg.getStaticSharedLibName(),
+ if (pkg.getStaticSharedLibraryName() != null) {
+ if (mSharedLibraries.removeSharedLibrary(pkg.getStaticSharedLibraryName(),
pkg.getStaticSharedLibVersion())) {
if (DEBUG_REMOVE && chatty) {
if (r == null) {
@@ -237,7 +237,7 @@
} else {
r.append(' ');
}
- r.append(pkg.getStaticSharedLibName());
+ r.append(pkg.getStaticSharedLibraryName());
}
}
}
@@ -271,7 +271,7 @@
outInfo.mRemovedPackage = packageName;
outInfo.mInstallerPackageName = deletedPs.getInstallSource().installerPackageName;
outInfo.mIsStaticSharedLib = deletedPkg != null
- && deletedPkg.getStaticSharedLibName() != null;
+ && deletedPkg.getStaticSharedLibraryName() != null;
outInfo.populateUsers(deletedPs.queryInstalledUsers(
mUserManagerInternal.getUserIds(), true), deletedPs);
outInfo.mIsExternal = deletedPs.isExternalStorage();
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 9bd8e12..bce6834 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -445,11 +445,11 @@
}
SharedLibraryInfo sdkLibraryInfo = null;
- if (!TextUtils.isEmpty(parsedPackage.getSdkLibName())) {
+ if (!TextUtils.isEmpty(parsedPackage.getSdkLibraryName())) {
sdkLibraryInfo = AndroidPackageUtils.createSharedLibraryForSdk(parsedPackage);
}
SharedLibraryInfo staticSharedLibraryInfo = null;
- if (!TextUtils.isEmpty(parsedPackage.getStaticSharedLibName())) {
+ if (!TextUtils.isEmpty(parsedPackage.getStaticSharedLibraryName())) {
staticSharedLibraryInfo =
AndroidPackageUtils.createSharedLibraryForStatic(parsedPackage);
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 80e9646..0558fbd 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4667,17 +4667,17 @@
pw.println(libraryNames.get(i));
}
}
- if (pkg.getStaticSharedLibName() != null) {
+ if (pkg.getStaticSharedLibraryName() != null) {
pw.print(prefix); pw.println(" static library:");
pw.print(prefix); pw.print(" ");
- pw.print("name:"); pw.print(pkg.getStaticSharedLibName());
+ pw.print("name:"); pw.print(pkg.getStaticSharedLibraryName());
pw.print(" version:"); pw.println(pkg.getStaticSharedLibVersion());
}
- if (pkg.getSdkLibName() != null) {
+ if (pkg.getSdkLibraryName() != null) {
pw.print(prefix); pw.println(" SDK library:");
pw.print(prefix); pw.print(" ");
- pw.print("name:"); pw.print(pkg.getSdkLibName());
+ pw.print("name:"); pw.print(pkg.getSdkLibraryName());
pw.print(" versionMajor:"); pw.println(pkg.getSdkLibVersionMajor());
}
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 5905741..094e748 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -400,7 +400,7 @@
@Nullable
private SharedLibraryInfo getLatestStaticSharedLibraVersionLPr(@NonNull AndroidPackage pkg) {
WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(
- pkg.getStaticSharedLibName());
+ pkg.getStaticSharedLibraryName());
if (versionedLib == null) {
return null;
}
@@ -457,15 +457,15 @@
// - Package manager is in a state where package isn't scanned yet. This will
// get called again after scanning to fix the dependencies.
if (AndroidPackageUtils.isLibrary(pkg)) {
- if (pkg.getSdkLibName() != null) {
+ if (pkg.getSdkLibraryName() != null) {
SharedLibraryInfo definedLibrary = getSharedLibraryInfo(
- pkg.getSdkLibName(), pkg.getSdkLibVersionMajor());
+ pkg.getSdkLibraryName(), pkg.getSdkLibVersionMajor());
if (definedLibrary != null) {
action.accept(definedLibrary, libInfo);
}
- } else if (pkg.getStaticSharedLibName() != null) {
+ } else if (pkg.getStaticSharedLibraryName() != null) {
SharedLibraryInfo definedLibrary = getSharedLibraryInfo(
- pkg.getStaticSharedLibName(), pkg.getStaticSharedLibVersion());
+ pkg.getStaticSharedLibraryName(), pkg.getStaticSharedLibVersion());
if (definedLibrary != null) {
action.accept(definedLibrary, libInfo);
}
@@ -691,9 +691,9 @@
&& !hasString(pkg.getUsesLibraries(), changingPkg.getLibraryNames())
&& !hasString(pkg.getUsesOptionalLibraries(), changingPkg.getLibraryNames())
&& !ArrayUtils.contains(pkg.getUsesStaticLibraries(),
- changingPkg.getStaticSharedLibName())
+ changingPkg.getStaticSharedLibraryName())
&& !ArrayUtils.contains(pkg.getUsesSdkLibraries(),
- changingPkg.getSdkLibName())) {
+ changingPkg.getSdkLibraryName())) {
continue;
}
if (resultList == null) {
diff --git a/services/core/java/com/android/server/pm/SharedLibraryUtils.java b/services/core/java/com/android/server/pm/SharedLibraryUtils.java
index 274870d..2c28791 100644
--- a/services/core/java/com/android/server/pm/SharedLibraryUtils.java
+++ b/services/core/java/com/android/server/pm/SharedLibraryUtils.java
@@ -20,6 +20,7 @@
import android.content.pm.SharedLibraryInfo;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.SharedLibraryWrapper;
import com.android.server.utils.WatchedLongSparseArray;
import java.util.ArrayList;
@@ -79,8 +80,8 @@
if (!pkgSetting.getTransientState().getUsesLibraryInfos().isEmpty()) {
ArrayList<SharedLibraryInfo> retValue = new ArrayList<>();
Set<String> collectedNames = new HashSet<>();
- for (SharedLibraryInfo info : pkgSetting.getTransientState().getUsesLibraryInfos()) {
- findSharedLibrariesRecursive(info, retValue, collectedNames);
+ for (SharedLibraryWrapper info : pkgSetting.getTransientState().getUsesLibraryInfos()) {
+ findSharedLibrariesRecursive(info.getInfo(), retValue, collectedNames);
}
return retValue;
} else {
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index c3eb2fd..51bb412 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -548,7 +548,7 @@
if (pkg.isSdkLibrary()) {
Slog.w(TAG, "Cannot suspend package: " + packageName
+ " providing SDK library: "
- + pkg.getSdkLibName());
+ + pkg.getSdkLibraryName());
continue;
}
// Cannot suspend static shared libs as they are considered
@@ -557,7 +557,7 @@
if (pkg.isStaticSharedLibrary()) {
Slog.w(TAG, "Cannot suspend package: " + packageName
+ " providing static shared library: "
- + pkg.getStaticSharedLibName());
+ + pkg.getStaticSharedLibraryName());
continue;
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index b620249..b977025 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -46,15 +46,6 @@
public @interface OwnerType {
}
- // TODO(b/245963156): move to Display.java (and @hide) if we decide to support profiles on MUMD
- /**
- * Used only when starting a profile (on systems that
- * {@link android.os.UserManager#isUsersOnSecondaryDisplaysSupported() support users running on
- * secondary displays}), to indicate the profile should be started in the same display as its
- * parent user.
- */
- public static final int PARENT_DISPLAY = -2;
-
public interface UserRestrictionsListener {
/**
* Called when a user restriction changes.
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c77459d..ff87be99 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -6822,13 +6822,25 @@
Slogf.d(LOG_TAG, "assignUserToDisplay(%d, %d)", userId, displayId);
}
+ // NOTE: Using Boolean instead of boolean as it will be re-used below
+ Boolean isProfile = null;
if (displayId == Display.DEFAULT_DISPLAY) {
- // Don't need to do anything because methods (such as isUserVisible()) already know
- // that the current user (and their profiles) is assigned to the default display.
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "ignoring on default display");
+ if (mUsersOnSecondaryDisplaysEnabled) {
+ // Profiles are only supported in the default display, but it cannot return yet
+ // as it needs to check if the parent is also assigned to the DEFAULT_DISPLAY
+ // (this is done indirectly below when it checks that the profile parent is the
+ // current user, as the current user is always assigned to the DEFAULT_DISPLAY).
+ isProfile = isProfileUnchecked(userId);
}
- return;
+ if (isProfile == null || !isProfile) {
+ // Don't need to do anything because methods (such as isUserVisible()) already
+ // know that the current user (and their profiles) is assigned to the default
+ // display.
+ if (DBG_MUMD) {
+ Slogf.d(LOG_TAG, "ignoring on default display");
+ }
+ return;
+ }
}
if (!mUsersOnSecondaryDisplaysEnabled) {
@@ -6846,18 +6858,21 @@
Preconditions.checkArgument(userId != currentUserId,
"Cannot assign current user (%d) to other displays", currentUserId);
+ if (isProfile == null) {
+ isProfile = isProfileUnchecked(userId);
+ }
synchronized (mUsersOnSecondaryDisplays) {
- if (isProfileUnchecked(userId)) {
- // Profile can only start in the same display as parent
- Preconditions.checkArgument(displayId == UserManagerInternal.PARENT_DISPLAY,
- "Profile user can only be started in the same display as parent");
+ if (isProfile) {
+ // Profile can only start in the same display as parent. And for simplicity,
+ // that display must be the DEFAULT_DISPLAY.
+ Preconditions.checkArgument(displayId == Display.DEFAULT_DISPLAY,
+ "Profile user can only be started in the default display");
int parentUserId = getProfileParentId(userId);
- int parentDisplayId = mUsersOnSecondaryDisplays.get(parentUserId);
+ Preconditions.checkArgument(parentUserId == currentUserId,
+ "Only profile of current user can be assigned to a display");
if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "Adding profile user %d -> display %d", userId,
- parentDisplayId);
+ Slogf.d(LOG_TAG, "Ignoring profile user %d on default display", userId);
}
- mUsersOnSecondaryDisplays.put(userId, parentDisplayId);
return;
}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 1084145..bc3d7a6 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -81,6 +81,7 @@
import libcore.util.EmptyArray;
import java.io.File;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -472,7 +473,11 @@
PackageStateUnserialized pkgState = pkgSetting.getTransientState();
info.hiddenUntilInstalled = pkgState.isHiddenUntilInstalled();
List<String> usesLibraryFiles = pkgState.getUsesLibraryFiles();
- List<SharedLibraryInfo> usesLibraryInfos = pkgState.getUsesLibraryInfos();
+ var usesLibraries = pkgState.getUsesLibraryInfos();
+ var usesLibraryInfos = new ArrayList<SharedLibraryInfo>();
+ for (int index = 0; index < usesLibraries.size(); index++) {
+ usesLibraryInfos.add(usesLibraries.get(index).getInfo());
+ }
info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
? null : usesLibraryFiles.toArray(new String[0]);
info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos;
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index f6585f6..ca8ba6c 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -91,7 +91,7 @@
public static SharedLibraryInfo createSharedLibraryForSdk(AndroidPackage pkg) {
return new SharedLibraryInfo(null, pkg.getPackageName(),
AndroidPackageUtils.getAllCodePaths(pkg),
- pkg.getSdkLibName(),
+ pkg.getSdkLibraryName(),
pkg.getSdkLibVersionMajor(),
SharedLibraryInfo.TYPE_SDK_PACKAGE,
new VersionedPackage(pkg.getManifestPackageName(),
@@ -102,7 +102,7 @@
public static SharedLibraryInfo createSharedLibraryForStatic(AndroidPackage pkg) {
return new SharedLibraryInfo(null, pkg.getPackageName(),
AndroidPackageUtils.getAllCodePaths(pkg),
- pkg.getStaticSharedLibName(),
+ pkg.getStaticSharedLibraryName(),
pkg.getStaticSharedLibVersion(),
SharedLibraryInfo.TYPE_STATIC,
new VersionedPackage(pkg.getManifestPackageName(),
@@ -230,7 +230,7 @@
public static boolean isLibrary(AndroidPackage pkg) {
// TODO(b/135203078): Can parsing just enforce these always match?
- return pkg.getSdkLibName() != null || pkg.getStaticSharedLibName() != null
+ return pkg.getSdkLibraryName() != null || pkg.getStaticSharedLibraryName() != null
|| !pkg.getLibraryNames().isEmpty();
}
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index fe63dec..a43b979 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -57,6 +57,8 @@
import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.AndroidPackageSplitImpl;
import com.android.server.pm.pkg.SELinuxUtil;
import com.android.server.pm.pkg.component.ComponentMutateUtils;
import com.android.server.pm.pkg.component.ParsedActivity;
@@ -90,6 +92,7 @@
import java.io.File;
import java.security.PublicKey;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -235,11 +238,11 @@
private Map<String, String> overlayables = emptyMap();
@Nullable
@DataClass.ParcelWith(ForInternedString.class)
- private String sdkLibName;
+ private String sdkLibraryName;
private int sdkLibVersionMajor;
@Nullable
@DataClass.ParcelWith(ForInternedString.class)
- private String staticSharedLibName;
+ private String staticSharedLibraryName;
private long staticSharedLibVersion;
@NonNull
@DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringList.class)
@@ -399,6 +402,8 @@
private long mLongVersionCode;
private int mLocaleConfigRes;
+ private List<AndroidPackageSplit> mSplits;
+
@NonNull
public static PackageImpl forParsing(@NonNull String packageName, @NonNull String baseCodePath,
@NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp) {
@@ -775,6 +780,51 @@
}
@Override
+ public List<AndroidPackageSplit> getSplits() {
+ if (mSplits == null) {
+ var splits = new ArrayList<AndroidPackageSplit>();
+ splits.add(new AndroidPackageSplitImpl(
+ null,
+ getBaseApkPath(),
+ getBaseRevisionCode(),
+ isHasCode() ? ApplicationInfo.FLAG_HAS_CODE : 0,
+ getClassLoaderName()
+ ));
+
+ if (splitNames != null) {
+ for (int index = 0; index < splitNames.length; index++) {
+ splits.add(new AndroidPackageSplitImpl(
+ splitNames[index],
+ splitCodePaths[index],
+ splitRevisionCodes[index],
+ splitFlags[index],
+ splitClassLoaderNames[index]
+ ));
+ }
+ }
+
+ if (splitDependencies != null) {
+ for (int index = 0; index < splitDependencies.size(); index++) {
+ var splitIndex = splitDependencies.keyAt(index);
+ var dependenciesByIndex = splitDependencies.valueAt(index);
+ var dependencies = new ArrayList<AndroidPackageSplit>();
+ for (int dependencyIndex : dependenciesByIndex) {
+ // Legacy holdover, base dependencies are an array of -1 rather than empty
+ if (dependencyIndex >= 0) {
+ dependencies.add(splits.get(dependencyIndex));
+ }
+ }
+ ((AndroidPackageSplitImpl) splits.get(splitIndex))
+ .fillDependencies(Collections.unmodifiableList(dependencies));
+ }
+ }
+
+ mSplits = Collections.unmodifiableList(splits);
+ }
+ return mSplits;
+ }
+
+ @Override
public String toString() {
return "Package{"
+ Integer.toHexString(System.identityHashCode(this))
@@ -1209,8 +1259,8 @@
@Nullable
@Override
- public String getSdkLibName() {
- return sdkLibName;
+ public String getSdkLibraryName() {
+ return sdkLibraryName;
}
@Override
@@ -1279,8 +1329,8 @@
@Nullable
@Override
- public String getStaticSharedLibName() {
- return staticSharedLibName;
+ public String getStaticSharedLibraryName() {
+ return staticSharedLibraryName;
}
@Override
@@ -2218,8 +2268,8 @@
}
@Override
- public PackageImpl setSdkLibName(String sdkLibName) {
- this.sdkLibName = TextUtils.safeIntern(sdkLibName);
+ public PackageImpl setSdkLibraryName(String sdkLibraryName) {
+ this.sdkLibraryName = TextUtils.safeIntern(sdkLibraryName);
return this;
}
@@ -2261,8 +2311,8 @@
}
@Override
- public PackageImpl setStaticSharedLibName(String staticSharedLibName) {
- this.staticSharedLibName = TextUtils.safeIntern(staticSharedLibName);
+ public PackageImpl setStaticSharedLibraryName(String staticSharedLibraryName) {
+ this.staticSharedLibraryName = TextUtils.safeIntern(staticSharedLibraryName);
return this;
}
@@ -2977,9 +3027,9 @@
dest.writeString(this.overlayCategory);
dest.writeInt(this.overlayPriority);
sForInternedStringValueMap.parcel(this.overlayables, dest, flags);
- sForInternedString.parcel(this.sdkLibName, dest, flags);
+ sForInternedString.parcel(this.sdkLibraryName, dest, flags);
dest.writeInt(this.sdkLibVersionMajor);
- sForInternedString.parcel(this.staticSharedLibName, dest, flags);
+ sForInternedString.parcel(this.staticSharedLibraryName, dest, flags);
dest.writeLong(this.staticSharedLibVersion);
sForInternedStringList.parcel(this.libraryNames, dest, flags);
sForInternedStringList.parcel(this.usesLibraries, dest, flags);
@@ -3127,9 +3177,9 @@
this.overlayCategory = in.readString();
this.overlayPriority = in.readInt();
this.overlayables = sForInternedStringValueMap.unparcel(in);
- this.sdkLibName = sForInternedString.unparcel(in);
+ this.sdkLibraryName = sForInternedString.unparcel(in);
this.sdkLibVersionMajor = in.readInt();
- this.staticSharedLibName = sForInternedString.unparcel(in);
+ this.staticSharedLibraryName = sForInternedString.unparcel(in);
this.staticSharedLibVersion = in.readLong();
this.libraryNames = sForInternedStringList.unparcel(in);
this.usesLibraries = sForInternedStringList.unparcel(in);
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
index e07b77e..5108fcd 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -63,6 +63,69 @@
@Immutable
public interface AndroidPackage {
+ /**
+ * Library names this package is declared as, for use by other packages with "uses-library".
+ *
+ * @see R.styleable#AndroidManifestLibrary
+ */
+ @NonNull
+ List<String> getLibraryNames();
+
+ /**
+ * @see R.styleable#AndroidManifestSdkLibrary_name
+ */
+ @Nullable
+ String getSdkLibraryName();
+
+ /**
+ * @return List of all splits for a package. Note that base.apk is considered a
+ * split and will be provided as index 0 of the list.
+ */
+ @NonNull
+ List<AndroidPackageSplit> getSplits();
+
+ /**
+ * @see R.styleable#AndroidManifestStaticLibrary_name
+ */
+ @Nullable
+ String getStaticSharedLibraryName();
+
+ /**
+ * @see ApplicationInfo#targetSdkVersion
+ * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion
+ */
+ int getTargetSdkVersion();
+
+ /**
+ * @see ApplicationInfo#FLAG_DEBUGGABLE
+ */
+ boolean isDebuggable();
+
+ /**
+ * @see ApplicationInfo#PRIVATE_FLAG_ISOLATED_SPLIT_LOADING
+ */
+ boolean isIsolatedSplitLoading();
+
+ /**
+ * @see ApplicationInfo#PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY
+ */
+ boolean isSignedWithPlatformKey();
+
+ /**
+ * @see ApplicationInfo#PRIVATE_FLAG_USE_EMBEDDED_DEX
+ */
+ boolean isUseEmbeddedDex();
+
+ /**
+ * @see ApplicationInfo#PRIVATE_FLAG_USES_NON_SDK_API
+ */
+ boolean isUsesNonSdkApi();
+
+ /**
+ * @see ApplicationInfo#FLAG_VM_SAFE_MODE
+ */
+ boolean isVmSafeMode();
+
// Methods below this comment are not yet exposed as API
/**
@@ -317,15 +380,6 @@
int getLargestWidthLimitDp();
/**
- * Library names this package is declared as, for use by other packages with "uses-library".
- *
- * @see R.styleable#AndroidManifestLibrary
- * @hide
- */
- @NonNull
- List<String> getLibraryNames();
-
- /**
* The resource ID used to provide the application's locales configuration.
*
* @see R.styleable#AndroidManifestApplication_localeConfig
@@ -730,13 +784,6 @@
int getRoundIconRes();
/**
- * @see R.styleable#AndroidManifestSdkLibrary_name
- * @hide
- */
- @Nullable
- String getSdkLibName();
-
- /**
* @see R.styleable#AndroidManifestSdkLibrary_versionMajor
* @hide
*/
@@ -844,13 +891,6 @@
int[] getSplitRevisionCodes();
/**
- * @see R.styleable#AndroidManifestStaticLibrary_name
- * @hide
- */
- @Nullable
- String getStaticSharedLibName();
-
- /**
* @see R.styleable#AndroidManifestStaticLibrary_version
* @hide
*/
@@ -864,13 +904,6 @@
int getTargetSandboxVersion();
/**
- * @see ApplicationInfo#targetSdkVersion
- * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion
- * @hide
- */
- int getTargetSdkVersion();
-
- /**
* @see ApplicationInfo#taskAffinity
* @see R.styleable#AndroidManifestApplication_taskAffinity
* @hide
@@ -1118,12 +1151,6 @@
boolean isCrossProfile();
/**
- * @see ApplicationInfo#FLAG_DEBUGGABLE
- * @hide
- */
- boolean isDebuggable();
-
- /**
* @see ApplicationInfo#PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE
* @hide
*/
@@ -1198,12 +1225,6 @@
boolean isHasFragileUserData();
/**
- * @see ApplicationInfo#PRIVATE_FLAG_ISOLATED_SPLIT_LOADING
- * @hide
- */
- boolean isIsolatedSplitLoading();
-
- /**
* @see ApplicationInfo#FLAG_KILL_AFTER_RESTORE
* @hide
*/
@@ -1354,12 +1375,6 @@
boolean isSdkLibrary();
/**
- * @see ApplicationInfo#PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY
- * @hide
- */
- boolean isSignedWithPlatformKey();
-
- /**
* @see ApplicationInfo#PRIVATE_FLAG_STATIC_SHARED_LIBRARY
* @hide
*/
@@ -1445,24 +1460,12 @@
boolean isUse32BitAbi();
/**
- * @see ApplicationInfo#PRIVATE_FLAG_USE_EMBEDDED_DEX
- * @hide
- */
- boolean isUseEmbeddedDex();
-
- /**
* @see ApplicationInfo#FLAG_USES_CLEARTEXT_TRAFFIC
* @hide
*/
boolean isUsesCleartextTraffic();
/**
- * @see ApplicationInfo#PRIVATE_FLAG_USES_NON_SDK_API
- * @hide
- */
- boolean isUsesNonSdkApi();
-
- /**
* @see ApplicationInfo#PRIVATE_FLAG_VENDOR
* @hide
*/
@@ -1477,10 +1480,4 @@
* @hide
*/
boolean isVisibleToInstantApps();
-
- /**
- * @see ApplicationInfo#FLAG_VM_SAFE_MODE
- * @hide
- */
- boolean isVmSafeMode();
}
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackageSplit.java b/services/core/java/com/android/server/pm/pkg/AndroidPackageSplit.java
new file mode 100644
index 0000000..a17ecc3
--- /dev/null
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackageSplit.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.pkg;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.processor.immutability.Immutable;
+
+import java.util.List;
+
+/** @hide */
+@Immutable
+public interface AndroidPackageSplit {
+
+ @Nullable
+ String getName();
+
+ @NonNull
+ String getPath();
+
+ int getRevisionCode();
+
+ boolean isHasCode();
+
+ @Nullable
+ String getClassLoaderName();
+
+ @NonNull
+ List<AndroidPackageSplit> getDependencies();
+}
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackageSplitImpl.java b/services/core/java/com/android/server/pm/pkg/AndroidPackageSplitImpl.java
new file mode 100644
index 0000000..9aac8a8
--- /dev/null
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackageSplitImpl.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.pkg;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+public class AndroidPackageSplitImpl implements AndroidPackageSplit {
+
+ @Nullable
+ private final String mName;
+ @NonNull
+ private final String mPath;
+ private final int mRevisionCode;
+ private final int mFlags;
+ @Nullable
+ private final String mClassLoaderName;
+
+ @NonNull
+ private List<AndroidPackageSplit> mDependencies = Collections.emptyList();
+
+ public AndroidPackageSplitImpl(@Nullable String name, @NonNull String path, int revisionCode,
+ int flags, @Nullable String classLoaderName) {
+ mName = name;
+ mPath = path;
+ mRevisionCode = revisionCode;
+ mFlags = flags;
+ mClassLoaderName = classLoaderName;
+ }
+
+ public void fillDependencies(@NonNull List<AndroidPackageSplit> splits) {
+ if (!mDependencies.isEmpty()) {
+ throw new IllegalStateException("Cannot fill split dependencies more than once");
+ }
+ mDependencies = splits;
+ }
+
+ @Nullable
+ @Override
+ public String getName() {
+ return mName;
+ }
+
+ @NonNull
+ @Override
+ public String getPath() {
+ return mPath;
+ }
+
+ @Override
+ public int getRevisionCode() {
+ return mRevisionCode;
+ }
+
+ @Override
+ public boolean isHasCode() {
+ return (mFlags & ApplicationInfo.FLAG_HAS_CODE) != 0;
+ }
+
+ @Nullable
+ @Override
+ public String getClassLoaderName() {
+ return mClassLoaderName;
+ }
+
+ @NonNull
+ @Override
+ public List<AndroidPackageSplit> getDependencies() {
+ return mDependencies;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof AndroidPackageSplitImpl)) return false;
+ AndroidPackageSplitImpl that = (AndroidPackageSplitImpl) o;
+ var fieldsEqual = mRevisionCode == that.mRevisionCode && mFlags == that.mFlags && Objects.equals(
+ mName, that.mName) && Objects.equals(mPath, that.mPath)
+ && Objects.equals(mClassLoaderName, that.mClassLoaderName);
+
+ if (!fieldsEqual) return false;
+ if (mDependencies.size() != that.mDependencies.size()) return false;
+
+ // Should be impossible, but to avoid circular dependencies,
+ // only search 1 level deep using split name
+ for (int index = 0; index < mDependencies.size(); index++) {
+ if (!Objects.equals(mDependencies.get(index).getName(),
+ that.mDependencies.get(index).getName())) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ // Should be impossible, but to avoid circular dependencies,
+ // only search 1 level deep using split name
+ var dependenciesHash = Objects.hash(mName, mPath, mRevisionCode, mFlags, mClassLoaderName);
+ for (int index = 0; index < mDependencies.size(); index++) {
+ var name = mDependencies.get(index).getName();
+ dependenciesHash = 31 * dependenciesHash + (name == null ? 0 : name.hashCode());
+ }
+ return dependenciesHash;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index c0e063d..a6e6016 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -22,8 +22,8 @@
import android.annotation.UserIdInt;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningInfo;
+import android.os.UserHandle;
import android.processor.immutability.Immutable;
import android.util.SparseArray;
@@ -42,8 +42,6 @@
@Immutable
public interface PackageState {
- // Methods below this comment are not yet exposed as API
-
/*
* Until immutability or read-only caching is enabled, {@link PackageSetting} cannot be
* returned directly, so {@link PackageStateImpl} is used to temporarily copy the data.
@@ -83,11 +81,9 @@
* Re-attaching the storage device to make the APK available should allow the user to use the
* app once the device reboots or otherwise re-scans it.
* <p/>
- * This can also occur in an device OTA situation where the package is no longer parseable on
- * an updated SDK version, causing it to be rejectd, but the state associated with it retained,
+ * This can also occur in an device OTA situation where the package is no longer parsable on
+ * an updated SDK version, causing it to be rejected, but the state associated with it retained,
* similarly to if the package had been uninstalled with the --keep-data option.
- *
- * @hide
*/
@Nullable
AndroidPackage getAndroidPackage();
@@ -95,12 +91,58 @@
/**
* The non-user-specific UID, or the UID if the user ID is
* {@link android.os.UserHandle#SYSTEM}.
- *
- * @hide
*/
int getAppId();
/**
+ * @see AndroidPackage#getPackageName()
+ */
+ @NonNull
+ String getPackageName();
+
+ /**
+ * @see ApplicationInfo#primaryCpuAbi
+ */
+ @Nullable
+ String getPrimaryCpuAbi();
+
+ /**
+ * @see ApplicationInfo#secondaryCpuAbi
+ */
+ @Nullable
+ String getSecondaryCpuAbi();
+
+ /**
+ * @see AndroidPackage#isPrivileged()
+ */
+ boolean isPrivileged();
+
+ /**
+ * @see AndroidPackage#isSystem()
+ */
+ boolean isSystem();
+
+ /**
+ * Whether this app is on the /data partition having been upgraded from a preinstalled app on a
+ * system partition.
+ */
+ boolean isUpdatedSystemApp();
+
+ /**
+ * @return State for a user or {@link PackageUserState#DEFAULT} if the state doesn't exist.
+ */
+ @NonNull
+ PackageUserState getStateForUser(@NonNull UserHandle user);
+
+ /**
+ * @see R.styleable#AndroidManifestUsesLibrary
+ */
+ @NonNull
+ List<SharedLibrary> getUsesLibraries();
+
+ // Methods below this comment are not yet exposed as API
+
+ /**
* Value set through {@link PackageManager#setApplicationCategoryHint(String, int)}. Only
* applied if the application itself does not declare a category.
*
@@ -165,13 +207,6 @@
Map<String, Set<String>> getMimeGroups();
/**
- * @see AndroidPackage#getPackageName()
- * @hide
- */
- @NonNull
- String getPackageName();
-
- /**
* @see AndroidPackage#getPath()
* @hide
*/
@@ -179,20 +214,6 @@
File getPath();
/**
- * @see ApplicationInfo#primaryCpuAbi
- * @hide
- */
- @Nullable
- String getPrimaryCpuAbi();
-
- /**
- * @see ApplicationInfo#secondaryCpuAbi
- * @hide
- */
- @Nullable
- String getSecondaryCpuAbi();
-
- /**
* Whether the package shares the same user ID as other packages
* @hide
*/
@@ -239,14 +260,6 @@
List<String> getUsesLibraryFiles();
/**
- * @see R.styleable#AndroidManifestUsesLibrary
- * @hide
- */
- @Immutable.Ignore
- @NonNull
- List<SharedLibraryInfo> getUsesLibraryInfos();
-
- /**
* @see R.styleable#AndroidManifestUsesSdkLibrary
* @hide
*/
@@ -327,12 +340,6 @@
boolean isOem();
/**
- * @see AndroidPackage#isPrivileged()
- * @hide
- */
- boolean isPrivileged();
-
- /**
* @see AndroidPackage#isProduct()
* @hide
*/
@@ -345,12 +352,6 @@
boolean isRequiredForSystemUser();
/**
- * @see AndroidPackage#isSystem()
- * @hide
- */
- boolean isSystem();
-
- /**
* @see AndroidPackage#isSystemExt()
* @hide
*/
@@ -363,14 +364,6 @@
boolean isUpdateAvailable();
/**
- * Whether this app is on the /data partition having been upgraded from a preinstalled app on a
- * system partition.
- *
- * @hide
- */
- boolean isUpdatedSystemApp();
-
- /**
* Whether this app is packaged in an updated apex.
*
* @hide
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index 28309c7..c6ce40e 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -21,9 +21,9 @@
import android.annotation.Nullable;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
-import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningInfo;
import android.content.pm.overlay.OverlayPaths;
+import android.os.UserHandle;
import android.util.ArraySet;
import android.util.SparseArray;
@@ -140,7 +140,7 @@
@NonNull
private final long[] mUsesStaticLibrariesVersions;
@NonNull
- private final List<SharedLibraryInfo> mUsesLibraryInfos;
+ private final List<SharedLibrary> mUsesLibraries;
@NonNull
private final List<String> mUsesLibraryFiles;
@NonNull
@@ -181,7 +181,7 @@
mUsesSdkLibrariesVersionsMajor = pkgState.getUsesSdkLibrariesVersionsMajor();
mUsesStaticLibraries = pkgState.getUsesStaticLibraries();
mUsesStaticLibrariesVersions = pkgState.getUsesStaticLibrariesVersions();
- mUsesLibraryInfos = Collections.unmodifiableList(pkgState.getUsesLibraryInfos());
+ mUsesLibraries = Collections.unmodifiableList(pkgState.getUsesLibraries());
mUsesLibraryFiles = Collections.unmodifiableList(pkgState.getUsesLibraryFiles());
setBoolean(Booleans.FORCE_QUERYABLE_OVERRIDE, pkgState.isForceQueryableOverride());
setBoolean(Booleans.HIDDEN_UNTIL_INSTALLED, pkgState.isHiddenUntilInstalled());
@@ -201,6 +201,13 @@
}
}
+ @NonNull
+ @Override
+ public PackageUserState getStateForUser(@NonNull UserHandle user) {
+ PackageUserState userState = getUserStates().get(user.getIdentifier());
+ return userState == null ? PackageUserState.DEFAULT : userState;
+ }
+
@Override
public boolean isExternalStorage() {
return getBoolean(Booleans.EXTERNAL_STORAGE);
@@ -469,8 +476,7 @@
}
@DataClass.Generated.Member
- public @NonNull
- ArraySet<String> getDisabledComponents() {
+ public @NonNull ArraySet<String> getDisabledComponents() {
return mDisabledComponents;
}
@@ -536,10 +542,10 @@
}
@DataClass.Generated(
- time = 1644270981508L,
+ time = 1661977809886L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
- inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull java.util.Set<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull java.util.Set<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final long mFirstInstallTime\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+ inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final long mFirstInstallTime\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
@Deprecated
private void __metadata() {}
@@ -660,8 +666,8 @@
}
@DataClass.Generated.Member
- public @NonNull List<SharedLibraryInfo> getUsesLibraryInfos() {
- return mUsesLibraryInfos;
+ public @NonNull List<SharedLibrary> getUsesLibraries() {
+ return mUsesLibraries;
}
@DataClass.Generated.Member
@@ -691,10 +697,10 @@
}
@DataClass.Generated(
- time = 1644270981543L,
+ time = 1661977809932L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
- inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final boolean mHasSharedUser\nprivate final int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mUsesLibraryInfos\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+ inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final boolean mHasSharedUser\nprivate final int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nprivate static final int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
index 1ae00d3..b22c038 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
@@ -29,7 +29,6 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.stream.Collectors;
/**
* For use by {@link PackageSetting} to maintain functionality that used to exist in PackageParser.
@@ -42,13 +41,13 @@
* @hide
*/
@DataClass(genSetters = true, genConstructor = false, genBuilder = false)
-@DataClass.Suppress({"setLastPackageUsageTimeInMills", "setPackageSetting"})
+@DataClass.Suppress({"setLastPackageUsageTimeInMills", "setPackageSetting", "setUsesLibraryInfos"})
public class PackageStateUnserialized {
private boolean hiddenUntilInstalled;
@NonNull
- private List<SharedLibraryInfo> usesLibraryInfos = emptyList();
+ private List<SharedLibraryWrapper> usesLibraryInfos = emptyList();
@NonNull
private List<String> usesLibraryFiles = emptyList();
@@ -72,7 +71,7 @@
}
@NonNull
- public PackageStateUnserialized addUsesLibraryInfo(@NonNull SharedLibraryInfo value) {
+ public PackageStateUnserialized addUsesLibraryInfo(@NonNull SharedLibraryWrapper value) {
usesLibraryInfos = CollectionUtils.add(usesLibraryInfos, value);
return this;
}
@@ -143,8 +142,16 @@
}
public @NonNull List<SharedLibraryInfo> getNonNativeUsesLibraryInfos() {
- return getUsesLibraryInfos().stream()
- .filter((l) -> !l.isNative()).collect(Collectors.toList());
+ var list = new ArrayList<SharedLibraryInfo>();
+ usesLibraryInfos = getUsesLibraryInfos();
+ for (int index = 0; index < usesLibraryInfos.size(); index++) {
+ var library = usesLibraryInfos.get(index);
+ if (!library.isNative()) {
+ list.add(library.getInfo());
+ }
+
+ }
+ return list;
}
public PackageStateUnserialized setHiddenUntilInstalled(boolean value) {
@@ -154,7 +161,11 @@
}
public PackageStateUnserialized setUsesLibraryInfos(@NonNull List<SharedLibraryInfo> value) {
- usesLibraryInfos = value;
+ var list = new ArrayList<SharedLibraryWrapper>();
+ for (int index = 0; index < value.size(); index++) {
+ list.add(new SharedLibraryWrapper(value.get(index)));
+ }
+ usesLibraryInfos = list;
mPackageSetting.onChanged();
return this;
}
@@ -216,7 +227,7 @@
}
@DataClass.Generated.Member
- public @NonNull List<SharedLibraryInfo> getUsesLibraryInfos() {
+ public @NonNull List<SharedLibraryWrapper> getUsesLibraryInfos() {
return usesLibraryInfos;
}
@@ -265,10 +276,10 @@
}
@DataClass.Generated(
- time = 1646203523807L,
+ time = 1661373697219L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java",
- inputSignatures = "private boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate boolean updatedSystemApp\nprivate boolean apkInApex\nprivate boolean apkInUpdatedApex\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate final @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\nprivate long[] lazyInitLastPackageUsageTimeInMills()\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic long getLatestPackageUseTimeInMills()\npublic long getLatestForegroundPackageUseTimeInMills()\npublic void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setApkInApex(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setApkInUpdatedApex(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
+ inputSignatures = "private boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibraryWrapper> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate boolean updatedSystemApp\nprivate boolean apkInApex\nprivate boolean apkInUpdatedApex\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate final @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryInfo(com.android.server.pm.pkg.SharedLibraryWrapper)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryFile(java.lang.String)\nprivate long[] lazyInitLastPackageUsageTimeInMills()\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic long getLatestPackageUseTimeInMills()\npublic long getLatestForegroundPackageUseTimeInMills()\npublic void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setApkInApex(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setApkInUpdatedApex(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
index a1b6f1d..a68e59b 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
@@ -33,12 +33,18 @@
*
* @hide
*/
-// TODO(b/173807334): Expose API
//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
@Immutable
public interface PackageUserState {
/**
+ * @return whether the package is marked as installed
+ */
+ boolean isInstalled();
+
+ // Methods below this comment are not yet exposed as API
+
+ /**
* @hide
*/
@NonNull
@@ -150,12 +156,6 @@
boolean isHidden();
/**
- * @return whether the package is marked as installed for all users
- * @hide
- */
- boolean isInstalled();
-
- /**
* @return whether the package is marked as an ephemeral app, which restricts permissions,
* features, visibility
* @hide
diff --git a/services/core/java/com/android/server/pm/pkg/SharedLibrary.java b/services/core/java/com/android/server/pm/pkg/SharedLibrary.java
new file mode 100644
index 0000000..20f05f6
--- /dev/null
+++ b/services/core/java/com/android/server/pm/pkg/SharedLibrary.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.pkg;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.VersionedPackage;
+import android.processor.immutability.Immutable;
+
+import java.util.List;
+
+/**
+ * @hide
+ */
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+public interface SharedLibrary {
+
+ /**
+ * @see SharedLibraryInfo#getPath()
+ */
+ @Nullable
+ String getPath();
+
+ /**
+ * @see SharedLibraryInfo#getPackageName()
+ */
+ @Nullable
+ String getPackageName();
+
+ /**
+ * @see SharedLibraryInfo#getName()
+ */
+ @Nullable
+ String getName();
+
+ /**
+ * @see SharedLibraryInfo#getAllCodePaths()
+ */
+ @NonNull
+ List<String> getAllCodePaths();
+
+ /**
+ * @see SharedLibraryInfo#getLongVersion()
+ */
+ long getVersion();
+
+ /**
+ * @see SharedLibraryInfo#getType()
+ */
+ int getType();
+
+ /**
+ * @see SharedLibraryInfo#isNative()
+ */
+ boolean isNative();
+
+ /**
+ * @see SharedLibraryInfo#getDeclaringPackage()
+ */
+ @Immutable.Policy(exceptions = {Immutable.Policy.Exception.FINAL_CLASSES_WITH_FINAL_FIELDS})
+ @NonNull
+ VersionedPackage getDeclaringPackage();
+
+ /**
+ * @see SharedLibraryInfo#getDependentPackages()
+ */
+ @Immutable.Policy(exceptions = {Immutable.Policy.Exception.FINAL_CLASSES_WITH_FINAL_FIELDS})
+ @NonNull
+ List<VersionedPackage> getDependentPackages();
+
+ /**
+ * @see SharedLibraryInfo#getDependencies()
+ */
+ @NonNull
+ List<SharedLibrary> getDependencies();
+}
diff --git a/services/core/java/com/android/server/pm/pkg/SharedLibraryWrapper.java b/services/core/java/com/android/server/pm/pkg/SharedLibraryWrapper.java
new file mode 100644
index 0000000..2f1fe1a
--- /dev/null
+++ b/services/core/java/com/android/server/pm/pkg/SharedLibraryWrapper.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.pkg;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.VersionedPackage;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** @hide */
+public class SharedLibraryWrapper implements SharedLibrary {
+
+ private final SharedLibraryInfo mInfo;
+
+ @Nullable
+ private List<SharedLibrary> cachedDependenciesList;
+
+ public SharedLibraryWrapper(@NonNull SharedLibraryInfo info) {
+ mInfo = info;
+ }
+
+ @NonNull
+ public SharedLibraryInfo getInfo() {
+ return mInfo;
+ }
+
+ @Override
+ public String getPath() {
+ return mInfo.getPath();
+ }
+
+ @Override
+ public String getPackageName() {
+ return mInfo.getPackageName();
+ }
+
+ @Override
+ public String getName() {
+ return mInfo.getName();
+ }
+
+ @Override
+ public List<String> getAllCodePaths() {
+ return Collections.unmodifiableList(mInfo.getAllCodePaths());
+ }
+
+ @Override
+ public long getVersion() {
+ return mInfo.getLongVersion();
+ }
+
+ @Override
+ public int getType() {
+ return mInfo.getType();
+ }
+
+ @Override
+ public boolean isNative() {
+ return mInfo.isNative();
+ }
+
+ @NonNull
+ @Override
+ public VersionedPackage getDeclaringPackage() {
+ return mInfo.getDeclaringPackage();
+ }
+
+ @NonNull
+ @Override
+ public List<VersionedPackage> getDependentPackages() {
+ return Collections.unmodifiableList(mInfo.getDependentPackages());
+ }
+
+ @NonNull
+ @Override
+ public List<SharedLibrary> getDependencies() {
+ if (cachedDependenciesList == null) {
+ var dependencies = mInfo.getDependencies();
+ if (dependencies == null) {
+ cachedDependenciesList = Collections.emptyList();
+ } else {
+ var list = new ArrayList<SharedLibrary>();
+ for (int index = 0; index < dependencies.size(); index++) {
+ list.add(new SharedLibraryWrapper(dependencies.get(index)));
+ }
+ cachedDependenciesList = Collections.unmodifiableList(list);
+ }
+ }
+ return cachedDependenciesList;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
index 1a46e20..2626bb4 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
@@ -147,7 +147,7 @@
ParsingPackage setSharedUserId(String sharedUserId);
- ParsingPackage setStaticSharedLibName(String staticSharedLibName);
+ ParsingPackage setStaticSharedLibraryName(String staticSharedLibName);
ParsingPackage setTaskAffinity(String taskAffinity);
@@ -221,7 +221,7 @@
ParsingPackage setRestoreAnyVersion(boolean restoreAnyVersion);
- ParsingPackage setSdkLibName(String sdkLibName);
+ ParsingPackage setSdkLibraryName(String sdkLibName);
ParsingPackage setSdkLibVersionMajor(int sdkLibVersionMajor);
@@ -458,7 +458,7 @@
Boolean getResizeableActivity();
@Nullable
- String getSdkLibName();
+ String getSdkLibraryName();
@NonNull
List<ParsedService> getServices();
@@ -473,7 +473,7 @@
String[] getSplitNames();
@Nullable
- String getStaticSharedLibName();
+ String getStaticSharedLibraryName();
int getTargetSdkVersion();
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index a8d48ae..952adda 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -2164,8 +2164,8 @@
}
}
- if (TextUtils.isEmpty(pkg.getStaticSharedLibName()) && TextUtils.isEmpty(
- pkg.getSdkLibName())) {
+ if (TextUtils.isEmpty(pkg.getStaticSharedLibraryName()) && TextUtils.isEmpty(
+ pkg.getSdkLibraryName())) {
// Add a hidden app detail activity to normal apps which forwards user to App Details
// page.
ParseResult<ParsedActivity> a = generateAppDetailsHiddenActivity(input, pkg);
@@ -2355,12 +2355,12 @@
PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID,
"sharedUserId not allowed in SDK library"
);
- } else if (pkg.getSdkLibName() != null) {
+ } else if (pkg.getSdkLibraryName() != null) {
return input.error("Multiple SDKs for package "
+ pkg.getPackageName());
}
- return input.success(pkg.setSdkLibName(lname.intern())
+ return input.success(pkg.setSdkLibraryName(lname.intern())
.setSdkLibVersionMajor(versionMajor)
.setSdkLibrary(true));
} finally {
@@ -2393,12 +2393,12 @@
PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID,
"sharedUserId not allowed in static shared library"
);
- } else if (pkg.getStaticSharedLibName() != null) {
+ } else if (pkg.getStaticSharedLibraryName() != null) {
return input.error("Multiple static-shared libs for package "
+ pkg.getPackageName());
}
- return input.success(pkg.setStaticSharedLibName(lname.intern())
+ return input.success(pkg.setStaticSharedLibraryName(lname.intern())
.setStaticSharedLibVersion(
PackageInfo.composeLongVersionCode(versionMajor, version))
.setStaticSharedLibrary(true));
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index dad9584..9336be5 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -20,10 +20,12 @@
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputManagerInternal;
import android.media.AudioManager;
@@ -32,6 +34,7 @@
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.BatteryStats;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IWakeLockCallback;
import android.os.Looper;
@@ -137,7 +140,9 @@
private final NotifierHandler mHandler;
private final Executor mBackgroundExecutor;
private final Intent mScreenOnIntent;
+ private final Bundle mScreenOnOptions;
private final Intent mScreenOffIntent;
+ private final Bundle mScreenOffOptions;
// True if the device should suspend when the screen is off due to proximity.
private final boolean mSuspendWhenScreenOffDueToProximityConfig;
@@ -199,10 +204,14 @@
mScreenOnIntent.addFlags(
Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
+ mScreenOnOptions = BroadcastOptions.makeRemovingMatchingFilter(
+ new IntentFilter(Intent.ACTION_SCREEN_OFF)).toBundle();
mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF);
mScreenOffIntent.addFlags(
Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
+ mScreenOffOptions = BroadcastOptions.makeRemovingMatchingFilter(
+ new IntentFilter(Intent.ACTION_SCREEN_ON)).toBundle();
mSuspendWhenScreenOffDueToProximityConfig = context.getResources().getBoolean(
com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity);
@@ -788,7 +797,8 @@
if (mActivityManagerInternal.isSystemReady()) {
mContext.sendOrderedBroadcastAsUser(mScreenOnIntent, UserHandle.ALL, null,
- mWakeUpBroadcastDone, mHandler, 0, null, null);
+ AppOpsManager.OP_NONE, mScreenOnOptions, mWakeUpBroadcastDone, mHandler,
+ 0, null, null);
} else {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2, 1);
sendNextBroadcast();
@@ -811,7 +821,8 @@
if (mActivityManagerInternal.isSystemReady()) {
mContext.sendOrderedBroadcastAsUser(mScreenOffIntent, UserHandle.ALL, null,
- mGoToSleepBroadcastDone, mHandler, 0, null, null);
+ AppOpsManager.OP_NONE, mScreenOffOptions, mGoToSleepBroadcastDone, mHandler,
+ 0, null, null);
} else {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3, 1);
sendNextBroadcast();
diff --git a/services/core/java/com/android/server/resources/OWNERS b/services/core/java/com/android/server/resources/OWNERS
new file mode 100644
index 0000000..7460a14
--- /dev/null
+++ b/services/core/java/com/android/server/resources/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 568761
+
+patb@google.com
+zyy@google.com
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 25ff023..0b28ba2 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -228,8 +228,8 @@
SurfaceControl dragSurface = null;
if (!mDragResult && (ws.mSession.mPid == mPid)) {
// Report unconsumed drop location back to the app that started the drag.
- x = mCurrentX;
- y = mCurrentY;
+ x = ws.translateToWindowX(mCurrentX);
+ y = ws.translateToWindowY(mCurrentY);
if (relinquishDragSurfaceToDragSource()) {
// If requested (and allowed), report the drag surface back to the app
// starting the drag to handle the return animation
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index b9739f03..e1a1f57 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -291,7 +291,11 @@
public void finishDrawing(IWindow window,
@Nullable SurfaceControl.Transaction postDrawTransaction, int seqId) {
if (DEBUG) Slog.v(TAG_WM, "IWindow finishDrawing called for " + window);
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "finishDrawing: " + mPackageName);
+ }
mService.finishDrawingWindow(this, window, postDrawTransaction, seqId);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4004d65..6074dc8 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4568,7 +4568,7 @@
float translateToWindowX(float x) {
float winX = x - mWindowFrames.mFrame.left;
if (mGlobalScale != 1f) {
- winX *= mGlobalScale;
+ winX *= mInvGlobalScale;
}
return winX;
}
@@ -4576,7 +4576,7 @@
float translateToWindowY(float y) {
float winY = y - mWindowFrames.mFrame.top;
if (mGlobalScale != 1f) {
- winY *= mGlobalScale;
+ winY *= mInvGlobalScale;
}
return winY;
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index b41fd39..1f66a11 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -17,12 +17,7 @@
package com.android.server.pm.test.parsing.parcelling
import android.content.Intent
-import android.content.pm.ApplicationInfo
-import android.content.pm.ConfigurationInfo
-import android.content.pm.FeatureGroupInfo
-import android.content.pm.FeatureInfo
-import android.content.pm.PackageManager
-import android.content.pm.SigningDetails
+import android.content.pm.*
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
@@ -32,18 +27,7 @@
import com.android.internal.R
import com.android.server.pm.parsing.pkg.PackageImpl
import com.android.server.pm.pkg.AndroidPackage
-import com.android.server.pm.pkg.component.ParsedActivityImpl
-import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl
-import com.android.server.pm.pkg.component.ParsedAttributionImpl
-import com.android.server.pm.pkg.component.ParsedComponentImpl
-import com.android.server.pm.pkg.component.ParsedInstrumentationImpl
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
-import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl
-import com.android.server.pm.pkg.component.ParsedPermissionImpl
-import com.android.server.pm.pkg.component.ParsedProcessImpl
-import com.android.server.pm.pkg.component.ParsedProviderImpl
-import com.android.server.pm.pkg.component.ParsedServiceImpl
-import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl
+import com.android.server.pm.pkg.component.*
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
import java.security.KeyPairGenerator
@@ -103,6 +87,7 @@
"getRequestedPermissions",
// Tested through asSplit
"asSplit",
+ "getSplits",
"getSplitNames",
"getSplitCodePaths",
"getSplitRevisionCodes",
@@ -175,9 +160,9 @@
AndroidPackage::getSecondaryNativeLibraryDir,
AndroidPackage::getSharedUserId,
AndroidPackage::getSharedUserLabel,
- AndroidPackage::getSdkLibName,
+ AndroidPackage::getSdkLibraryName,
AndroidPackage::getSdkLibVersionMajor,
- AndroidPackage::getStaticSharedLibName,
+ AndroidPackage::getStaticSharedLibraryName,
AndroidPackage::getStaticSharedLibVersion,
AndroidPackage::getTargetSandboxVersion,
AndroidPackage::getTargetSdkVersion,
@@ -550,6 +535,7 @@
SparseArray<IntArray>().apply {
put(0, intArrayOf(-1))
put(1, intArrayOf(0))
+ put(2, intArrayOf(1))
}
)
.setSplitHasCode(0, true)
@@ -599,9 +585,10 @@
expect.that(after.splitDependencies).isNotNull()
after.splitDependencies?.let {
- expect.that(it.size()).isEqualTo(2)
+ expect.that(it.size()).isEqualTo(3)
expect.that(it.get(0)).asList().containsExactly(-1)
expect.that(it.get(1)).asList().containsExactly(0)
+ expect.that(it.get(2)).asList().containsExactly(1)
}
expect.that(after.usesSdkLibraries).containsExactly("testSdk")
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
index 7e9e433..8a855e5 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
@@ -95,8 +95,6 @@
ParsedProvider::getUriPermissionPatterns,
ParsedService::getIntents,
ParsedService::getProperties,
- SharedLibraryInfo::getAllCodePaths,
- SharedLibraryInfo::getDependencies,
Intent::getCategories,
PackageUserState::getDisabledComponents,
PackageUserState::getEnabledComponents,
@@ -149,6 +147,20 @@
*/
private fun fillMissingData(pkgSetting: PackageSetting, pkg: PackageImpl) {
pkgSetting.addUsesLibraryFile("usesLibraryFile")
+
+ val sharedLibraryDependency = listOf(SharedLibraryInfo(
+ "pathDependency",
+ "packageNameDependency",
+ listOf(tempFolder.newFile().path),
+ "nameDependency",
+ 1,
+ 0,
+ VersionedPackage("versionedPackage0Dependency", 1),
+ listOf(VersionedPackage("versionedPackage1Dependency", 2)),
+ emptyList(),
+ false
+ ))
+
pkgSetting.addUsesLibraryInfo(SharedLibraryInfo(
"path",
"packageName",
@@ -158,7 +170,7 @@
0,
VersionedPackage("versionedPackage0", 1),
listOf(VersionedPackage("versionedPackage1", 2)),
- emptyList(),
+ sharedLibraryDependency,
false
))
pkgSetting.addMimeTypes("mimeGroup", setOf("mimeType"))
@@ -233,7 +245,13 @@
}
val value = try {
- collection.stream().findFirst().get()!!
+ if (AndroidPackage::getSplits == it) {
+ // The base split is defined to never have any dependencies,
+ // so force the visitor to use the split at index 1 instead of 0.
+ collection.last()
+ } else {
+ collection.first()
+ }
} catch (e: Exception) {
if (enforceNonEmpty) {
expect.withMessage("Method $newChainText ${it.name} returns empty")
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index af96346..e09b80e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -18,17 +18,33 @@
import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
+import static com.android.server.am.BroadcastQueueTest.CLASS_GREEN;
+import static com.android.server.am.BroadcastQueueTest.PACKAGE_BLUE;
+import static com.android.server.am.BroadcastQueueTest.PACKAGE_GREEN;
+import static com.android.server.am.BroadcastQueueTest.PACKAGE_RED;
+import static com.android.server.am.BroadcastQueueTest.PACKAGE_YELLOW;
+import static com.android.server.am.BroadcastQueueTest.getUidForPackage;
+import static com.android.server.am.BroadcastQueueTest.makeManifestReceiver;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.os.HandlerThread;
+import android.os.UserHandle;
import android.provider.Settings;
import androidx.test.filters.SmallTest;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -41,6 +57,8 @@
@SmallTest
@RunWith(MockitoJUnitRunner.class)
public class BroadcastQueueModernImplTest {
+ private static final int TEST_UID = android.os.Process.FIRST_APPLICATION_UID;
+
@Mock ActivityManagerService mAms;
@Mock BroadcastProcessQueue mQueue1;
@@ -49,6 +67,8 @@
@Mock BroadcastProcessQueue mQueue4;
HandlerThread mHandlerThread;
+
+ BroadcastConstants mConstants;
BroadcastQueueModernImpl mImpl;
BroadcastProcessQueue mHead;
@@ -59,9 +79,10 @@
mHandlerThread = new HandlerThread(getClass().getSimpleName());
mHandlerThread.start();
+
+ mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
mImpl = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
- new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS),
- new BroadcastConstants(Settings.Global.BROADCAST_BG_CONSTANTS));
+ mConstants, mConstants);
doReturn(1L).when(mQueue1).getRunnableAt();
doReturn(2L).when(mQueue2).getRunnableAt();
@@ -69,6 +90,11 @@
doReturn(4L).when(mQueue4).getRunnableAt();
}
+ @After
+ public void tearDown() throws Exception {
+ mHandlerThread.quit();
+ }
+
private static void assertOrphan(BroadcastProcessQueue queue) {
assertNull(queue.runnableAtNext);
assertNull(queue.runnableAtPrev);
@@ -94,8 +120,31 @@
}
}
+ private BroadcastRecord makeBroadcastRecord(Intent intent) {
+ return makeBroadcastRecord(intent, BroadcastOptions.makeBasic(),
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), false);
+ }
+
+ private BroadcastRecord makeOrderedBroadcastRecord(Intent intent) {
+ return makeBroadcastRecord(intent, BroadcastOptions.makeBasic(),
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), true);
+ }
+
+ private BroadcastRecord makeBroadcastRecord(Intent intent, BroadcastOptions options) {
+ return makeBroadcastRecord(intent, options,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), false);
+ }
+
+ private BroadcastRecord makeBroadcastRecord(Intent intent, BroadcastOptions options,
+ List receivers, boolean ordered) {
+ return new BroadcastRecord(mImpl, intent, null, PACKAGE_RED, null, 21, 42, false, null,
+ null, null, null, AppOpsManager.OP_NONE, options, receivers, null,
+ Activity.RESULT_OK, null, null, ordered, false, false, UserHandle.USER_SYSTEM,
+ false, null, false, null);
+ }
+
@Test
- public void testRunnableAt_Simple() {
+ public void testRunnableList_Simple() {
assertRunnableList(List.of(), mHead);
mHead = insertIntoRunnableList(mHead, mQueue1);
@@ -106,7 +155,7 @@
}
@Test
- public void testRunnableAt_InsertLast() {
+ public void testRunnableList_InsertLast() {
mHead = insertIntoRunnableList(mHead, mQueue1);
mHead = insertIntoRunnableList(mHead, mQueue2);
mHead = insertIntoRunnableList(mHead, mQueue3);
@@ -115,7 +164,7 @@
}
@Test
- public void testRunnableAt_InsertFirst() {
+ public void testRunnableList_InsertFirst() {
mHead = insertIntoRunnableList(mHead, mQueue4);
mHead = insertIntoRunnableList(mHead, mQueue3);
mHead = insertIntoRunnableList(mHead, mQueue2);
@@ -124,7 +173,7 @@
}
@Test
- public void testRunnableAt_InsertMiddle() {
+ public void testRunnableList_InsertMiddle() {
mHead = insertIntoRunnableList(mHead, mQueue1);
mHead = insertIntoRunnableList(mHead, mQueue3);
mHead = insertIntoRunnableList(mHead, mQueue2);
@@ -132,7 +181,7 @@
}
@Test
- public void testRunnableAt_Remove() {
+ public void testRunnableList_Remove() {
mHead = insertIntoRunnableList(mHead, mQueue1);
mHead = insertIntoRunnableList(mHead, mQueue2);
mHead = insertIntoRunnableList(mHead, mQueue3);
@@ -156,4 +205,128 @@
assertOrphan(mQueue3);
assertOrphan(mQueue4);
}
+
+ @Test
+ public void testProcessQueue_Complex() {
+ BroadcastProcessQueue red = mImpl.getOrCreateProcessQueue(PACKAGE_RED, TEST_UID);
+ BroadcastProcessQueue green = mImpl.getOrCreateProcessQueue(PACKAGE_GREEN, TEST_UID);
+ BroadcastProcessQueue blue = mImpl.getOrCreateProcessQueue(PACKAGE_BLUE, TEST_UID);
+
+ assertEquals(PACKAGE_RED, red.processName);
+ assertEquals(PACKAGE_GREEN, green.processName);
+ assertEquals(PACKAGE_BLUE, blue.processName);
+
+ // Verify that removing middle queue works
+ mImpl.removeProcessQueue(PACKAGE_GREEN, TEST_UID);
+ assertEquals(red, mImpl.getProcessQueue(PACKAGE_RED, TEST_UID));
+ assertNull(mImpl.getProcessQueue(PACKAGE_GREEN, TEST_UID));
+ assertEquals(blue, mImpl.getProcessQueue(PACKAGE_BLUE, TEST_UID));
+ assertNull(mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID));
+
+ // Verify that removing head queue works
+ mImpl.removeProcessQueue(PACKAGE_RED, TEST_UID);
+ assertNull(mImpl.getProcessQueue(PACKAGE_RED, TEST_UID));
+ assertNull(mImpl.getProcessQueue(PACKAGE_GREEN, TEST_UID));
+ assertEquals(blue, mImpl.getProcessQueue(PACKAGE_BLUE, TEST_UID));
+ assertNull(mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID));
+
+ // Verify that removing last queue works
+ mImpl.removeProcessQueue(PACKAGE_BLUE, TEST_UID);
+ assertNull(mImpl.getProcessQueue(PACKAGE_RED, TEST_UID));
+ assertNull(mImpl.getProcessQueue(PACKAGE_GREEN, TEST_UID));
+ assertNull(mImpl.getProcessQueue(PACKAGE_BLUE, TEST_UID));
+ assertNull(mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID));
+
+ // Verify that removing missing doesn't crash
+ mImpl.removeProcessQueue(PACKAGE_YELLOW, TEST_UID);
+
+ // Verify that we can start all over again safely
+ BroadcastProcessQueue yellow = mImpl.getOrCreateProcessQueue(PACKAGE_YELLOW, TEST_UID);
+ assertEquals(yellow, mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID));
+ }
+
+ /**
+ * Empty queue isn't runnable.
+ */
+ @Test
+ public void testRunnableAt_Empty() {
+ BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+ assertFalse(queue.isRunnable());
+ assertEquals(Long.MAX_VALUE, queue.getRunnableAt());
+ }
+
+ /**
+ * Queue with a "normal" broadcast is runnable at different times depending
+ * on process cached state; when cached it's delayed by some amount.
+ */
+ @Test
+ public void testRunnableAt_Normal() {
+ BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
+ queue.enqueueBroadcast(airplaneRecord, 0);
+
+ queue.setProcessCached(false);
+ final long notCachedRunnableAt = queue.getRunnableAt();
+ queue.setProcessCached(true);
+ final long cachedRunnableAt = queue.getRunnableAt();
+ assertTrue(cachedRunnableAt > notCachedRunnableAt);
+ }
+
+ /**
+ * Queue with foreground broadcast is always runnable immediately,
+ * regardless of process cached state.
+ */
+ @Test
+ public void testRunnableAt_Foreground() {
+ BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
+ queue.enqueueBroadcast(airplaneRecord, 0);
+
+ queue.setProcessCached(false);
+ assertTrue(queue.isRunnable());
+ assertEquals(airplaneRecord.enqueueTime, queue.getRunnableAt());
+
+ queue.setProcessCached(true);
+ assertTrue(queue.isRunnable());
+ assertEquals(airplaneRecord.enqueueTime, queue.getRunnableAt());
+ }
+
+ /**
+ * Verify that sending a broadcast that removes any matching pending
+ * broadcasts is applied as expected.
+ */
+ @Test
+ public void testRemoveMatchingFilter() {
+ final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON);
+ final BroadcastOptions optionsOn = BroadcastOptions.makeBasic();
+ optionsOn.setRemoveMatchingFilter(new IntentFilter(Intent.ACTION_SCREEN_OFF));
+
+ final Intent screenOff = new Intent(Intent.ACTION_SCREEN_OFF);
+ final BroadcastOptions optionsOff = BroadcastOptions.makeBasic();
+ optionsOff.setRemoveMatchingFilter(new IntentFilter(Intent.ACTION_SCREEN_ON));
+
+ // Halt all processing so that we get a consistent view
+ mHandlerThread.getLooper().getQueue().postSyncBarrier();
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff));
+
+ // Marching through the queue we should only have one SCREEN_OFF
+ // broadcast, since that's the last state we dispatched
+ final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_SCREEN_OFF, queue.getActive().intent.getAction());
+ assertTrue(queue.isEmpty());
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index d3ceec8..cf5d113 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -16,22 +16,34 @@
package com.android.server.am;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Activity;
+import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.IApplicationThread;
+import android.app.usage.UsageEvents.Event;
+import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
import android.content.IIntentReceiver;
@@ -39,12 +51,15 @@
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.PowerExemptionManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
@@ -58,6 +73,7 @@
import com.android.server.appop.AppOpsService;
import com.android.server.wm.ActivityTaskManagerService;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -65,14 +81,23 @@
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.mockito.ArgumentMatcher;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.verification.VerificationMode;
+import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.UnaryOperator;
/**
* Common tests for {@link BroadcastQueue} implementations.
@@ -104,6 +129,8 @@
private ProcessList mProcessList;
@Mock
private PackageManagerInternal mPackageManagerInt;
+ @Mock
+ private UsageStatsManagerInternal mUsageStatsManagerInt;
private ActivityManagerService mAms;
private BroadcastQueue mQueue;
@@ -113,6 +140,11 @@
*/
private SparseArray<ReceiverList> mRegisteredReceivers = new SparseArray<>();
+ /**
+ * Collection of all active processes during current test run.
+ */
+ private List<ProcessRecord> mActiveProcesses = new ArrayList<>();
+
@Parameters(name = "impl={0}")
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] { {Impl.DEFAULT}, {Impl.MODERN} });
@@ -136,13 +168,18 @@
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
doNothing().when(mPackageManagerInt).setPackageStoppedState(any(), anyBoolean(), anyInt());
+ doAnswer((invocation) -> {
+ return getUidForPackage(invocation.getArgument(0));
+ }).when(mPackageManagerInt).getPackageUid(any(), anyLong(), eq(UserHandle.USER_SYSTEM));
final ActivityManagerService realAms = new ActivityManagerService(
new TestInjector(mContext), mServiceThreadRule.getThread());
realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
+ realAms.mOomAdjuster.mCachedAppOptimizer = spy(realAms.mOomAdjuster.mCachedAppOptimizer);
realAms.mPackageManagerInt = mPackageManagerInt;
+ realAms.mUsageStatsService = mUsageStatsManagerInt;
realAms.mProcessesReady = true;
mAms = spy(realAms);
doAnswer((invocation) -> {
@@ -150,7 +187,8 @@
+ Arrays.toString(invocation.getArguments()));
final String processName = invocation.getArgument(0);
final ApplicationInfo ai = invocation.getArgument(1);
- final ProcessRecord res = makeActiveProcessRecord(ai, processName, false);
+ final ProcessRecord res = makeActiveProcessRecord(ai, processName, false,
+ false, UnaryOperator.identity());
mHandlerThread.getThreadHandler().post(() -> {
synchronized (mAms) {
mQueue.onApplicationAttachedLocked(res);
@@ -164,6 +202,7 @@
final BroadcastConstants constants = new BroadcastConstants(
Settings.Global.BROADCAST_FG_CONSTANTS);
constants.TIMEOUT = 100;
+ constants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
final BroadcastSkipPolicy emptySkipPolicy = new BroadcastSkipPolicy(mAms) {
public boolean shouldSkip(BroadcastRecord r, ResolveInfo info) {
return false;
@@ -189,6 +228,18 @@
}
}
+ @After
+ public void tearDown() throws Exception {
+ mHandlerThread.quit();
+
+ // Verify that all processes have finished handling broadcasts
+ for (ProcessRecord app : mActiveProcesses) {
+ assertTrue(app.toShortString(), app.mReceivers.numberOfCurReceivers() == 0);
+ assertTrue(app.toShortString(), mQueue.getPreferredSchedulingGroupLocked(app)
+ == ProcessList.SCHED_GROUP_UNDEFINED);
+ }
+ }
+
private class TestInjector extends Injector {
TestInjector(Context context) {
super(context);
@@ -210,20 +261,40 @@
}
}
- private ProcessRecord makeActiveProcessRecord(String packageName) throws Exception {
- final ApplicationInfo ai = makeApplicationInfo(packageName);
- return makeActiveProcessRecord(ai, ai.processName, false);
+ /**
+ * Helper that leverages try-with-resources to pause dispatch of
+ * {@link #mHandlerThread} until released.
+ */
+ private class SyncBarrier implements AutoCloseable {
+ private final int mToken;
+
+ public SyncBarrier() {
+ mToken = mHandlerThread.getLooper().getQueue().postSyncBarrier();
+ }
+
+ @Override
+ public void close() throws Exception {
+ mHandlerThread.getLooper().getQueue().removeSyncBarrier(mToken);
+ }
}
- private ProcessRecord makeActiveProcessRecordWedged(String packageName) throws Exception {
+ private ProcessRecord makeActiveProcessRecord(String packageName) throws Exception {
final ApplicationInfo ai = makeApplicationInfo(packageName);
- return makeActiveProcessRecord(ai, ai.processName, true);
+ return makeActiveProcessRecord(ai, ai.processName, false, false,
+ UnaryOperator.identity());
+ }
+
+ private ProcessRecord makeWedgedActiveProcessRecord(String packageName) throws Exception {
+ final ApplicationInfo ai = makeApplicationInfo(packageName);
+ return makeActiveProcessRecord(ai, ai.processName, true, false,
+ UnaryOperator.identity());
}
private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, String processName,
- boolean wedged) throws Exception {
- final ProcessRecord r = new ProcessRecord(mAms, ai, processName, ai.uid);
+ boolean wedged, boolean abort, UnaryOperator<Bundle> extrasOperator) throws Exception {
+ final ProcessRecord r = spy(new ProcessRecord(mAms, ai, processName, ai.uid));
r.setPid(mNextPid.getAndIncrement());
+ mActiveProcesses.add(r);
final IApplicationThread thread = mock(IApplicationThread.class);
final IBinder threadBinder = new Binder();
@@ -241,11 +312,15 @@
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting scheduleReceiver() for "
+ Arrays.toString(invocation.getArguments()));
+ final Bundle extras = invocation.getArgument(5);
if (!wedged) {
+ assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
+ assertTrue(mQueue.getPreferredSchedulingGroupLocked(r)
+ != ProcessList.SCHED_GROUP_UNDEFINED);
mHandlerThread.getThreadHandler().post(() -> {
synchronized (mAms) {
mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
- null, null, false, false);
+ null, extrasOperator.apply(extras), abort, false);
}
});
}
@@ -256,12 +331,16 @@
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for "
+ Arrays.toString(invocation.getArguments()));
+ final Bundle extras = invocation.getArgument(4);
final boolean ordered = invocation.getArgument(5);
if (!wedged && ordered) {
+ assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
+ assertTrue(mQueue.getPreferredSchedulingGroupLocked(r)
+ != ProcessList.SCHED_GROUP_UNDEFINED);
mHandlerThread.getThreadHandler().post(() -> {
synchronized (mAms) {
mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
- null, null, false, false);
+ null, extrasOperator.apply(extras), abort, false);
}
});
}
@@ -272,7 +351,7 @@
return r;
}
- private ApplicationInfo makeApplicationInfo(String packageName) {
+ static ApplicationInfo makeApplicationInfo(String packageName) {
final ApplicationInfo ai = new ApplicationInfo();
ai.packageName = packageName;
ai.processName = packageName;
@@ -280,7 +359,7 @@
return ai;
}
- private ResolveInfo makeManifestReceiver(String packageName, String name) {
+ static ResolveInfo makeManifestReceiver(String packageName, String name) {
final ResolveInfo ri = new ResolveInfo();
ri.activityInfo = new ActivityInfo();
ri.activityInfo.packageName = packageName;
@@ -302,15 +381,35 @@
private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp,
List receivers) {
- return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(), receivers);
+ return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(),
+ receivers, false, null, null);
+ }
+
+ private BroadcastRecord makeOrderedBroadcastRecord(Intent intent, ProcessRecord callerApp,
+ List receivers, IIntentReceiver orderedResultTo, Bundle orderedExtras) {
+ return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(),
+ receivers, true, orderedResultTo, orderedExtras);
}
private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp,
BroadcastOptions options, List receivers) {
+ return makeBroadcastRecord(intent, callerApp, options, receivers, false, null, null);
+ }
+
+ private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp,
+ BroadcastOptions options, List receivers, boolean ordered,
+ IIntentReceiver orderedResultTo, Bundle orderedExtras) {
return new BroadcastRecord(mQueue, intent, callerApp, callerApp.info.packageName, null,
callerApp.getPid(), callerApp.info.uid, false, null, null, null, null,
- AppOpsManager.OP_NONE, options, receivers, null, Activity.RESULT_OK, null, null,
- false, false, false, UserHandle.USER_SYSTEM, false, null, false, null);
+ AppOpsManager.OP_NONE, options, receivers, orderedResultTo, Activity.RESULT_OK,
+ null, orderedExtras, ordered, false, false, UserHandle.USER_SYSTEM, false, null,
+ false, null);
+ }
+
+ private ArgumentMatcher<Intent> filterEquals(Intent intent) {
+ return (test) -> {
+ return intent.filterEquals(test);
+ };
}
private ArgumentMatcher<Intent> filterEqualsIgnoringComponent(Intent intent) {
@@ -323,6 +422,17 @@
};
}
+ private ArgumentMatcher<Bundle> bundleEquals(Bundle bundle) {
+ return (test) -> {
+ // TODO: check values in addition to keys
+ return Objects.equals(test.keySet(), bundle.keySet());
+ };
+ }
+
+ private @NonNull Bundle clone(@Nullable Bundle b) {
+ return (b != null) ? new Bundle(b) : new Bundle();
+ }
+
private void enqueueBroadcast(BroadcastRecord r) {
synchronized (mAms) {
mQueue.enqueueBroadcastLocked(r);
@@ -339,6 +449,15 @@
any(), eq(false), eq(UserHandle.USER_SYSTEM), anyInt());
}
+ private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app, Intent intent,
+ ComponentName component) throws Exception {
+ final Intent targetedIntent = new Intent(intent);
+ targetedIntent.setComponent(component);
+ verify(app.getThread(), mode).scheduleReceiver(
+ argThat(filterEquals(targetedIntent)), any(), any(), anyInt(), any(),
+ any(), eq(false), eq(UserHandle.USER_SYSTEM), anyInt());
+ }
+
private void verifyScheduleRegisteredReceiver(ProcessRecord app, Intent intent)
throws Exception {
verify(app.getThread()).scheduleRegisteredReceiver(any(),
@@ -346,17 +465,19 @@
anyBoolean(), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
}
- private static final String PACKAGE_RED = "com.example.red";
- private static final String PACKAGE_GREEN = "com.example.green";
- private static final String PACKAGE_BLUE = "com.example.blue";
- private static final String PACKAGE_YELLOW = "com.example.yellow";
+ static final int USER_GUEST = 11;
- private static final String CLASS_RED = "com.example.red.Red";
- private static final String CLASS_GREEN = "com.example.green.Green";
- private static final String CLASS_BLUE = "com.example.blue.Blue";
- private static final String CLASS_YELLOW = "com.example.yellow.Yellow";
+ static final String PACKAGE_RED = "com.example.red";
+ static final String PACKAGE_GREEN = "com.example.green";
+ static final String PACKAGE_BLUE = "com.example.blue";
+ static final String PACKAGE_YELLOW = "com.example.yellow";
- private static int getUidForPackage(String packageName) {
+ static final String CLASS_RED = "com.example.red.Red";
+ static final String CLASS_GREEN = "com.example.green.Green";
+ static final String CLASS_BLUE = "com.example.blue.Blue";
+ static final String CLASS_YELLOW = "com.example.yellow.Yellow";
+
+ static int getUidForPackage(@NonNull String packageName) {
switch (packageName) {
case PACKAGE_RED: return android.os.Process.FIRST_APPLICATION_UID + 1;
case PACKAGE_GREEN: return android.os.Process.FIRST_APPLICATION_UID + 2;
@@ -366,6 +487,14 @@
}
}
+ @Test
+ public void testDump() throws Exception {
+ // To maximize test coverage, dump current state; we're not worried
+ // about the actual output, just that we don't crash
+ mQueue.dumpLocked(FileDescriptor.err, new PrintWriter(new ByteArrayOutputStream()),
+ null, 0, true, null, false);
+ }
+
/**
* Verify dispatch of simple broadcast to single manifest receiver in
* already-running warm app.
@@ -501,7 +630,10 @@
makeRegisteredReceiver(receiverYellowApp))));
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ airplane.setComponent(new ComponentName(PACKAGE_YELLOW, CLASS_YELLOW));
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.recordResponseEventWhileInBackground(42L);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, options,
List.of(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
waitForIdle();
@@ -512,6 +644,43 @@
verifyScheduleReceiver(receiverBlueApp, timezone);
verifyScheduleRegisteredReceiver(receiverYellowApp, timezone);
verifyScheduleReceiver(receiverYellowApp, airplane);
+
+ for (ProcessRecord receiverApp : new ProcessRecord[] {
+ receiverGreenApp, receiverBlueApp, receiverYellowApp
+ }) {
+ // Confirm expected OOM adjustments; we were invoked once to upgrade
+ // and once to downgrade
+ assertEquals(ActivityManager.PROCESS_STATE_RECEIVER,
+ receiverApp.mState.getReportedProcState());
+ verify(mAms, times(2)).enqueueOomAdjTargetLocked(eq(receiverApp));
+
+ if ((mImpl == Impl.DEFAULT) && (receiverApp == receiverBlueApp)) {
+ // Nuance: the default implementation doesn't ask for manifest
+ // cold-started apps to be thawed, but the modern stack does
+ } else {
+ // Confirm that app was thawed
+ verify(mAms.mOomAdjuster.mCachedAppOptimizer).unfreezeTemporarily(eq(receiverApp),
+ eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER));
+
+ // Confirm that we added package to process
+ verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
+ anyLong(), any());
+ }
+
+ // Confirm that we've reported package as being used
+ verify(mAms, atLeastOnce()).notifyPackageUse(eq(receiverApp.info.packageName),
+ eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
+
+ // Confirm that we unstopped manifest receivers
+ verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState(
+ eq(receiverApp.info.packageName), eq(false), eq(UserHandle.USER_SYSTEM));
+ }
+
+ // Confirm that we've reported expected usage events
+ verify(mAms.mUsageStatsService).reportBroadcastDispatched(eq(callerApp.uid),
+ eq(PACKAGE_YELLOW), eq(UserHandle.SYSTEM), eq(42L), anyLong(), anyInt());
+ verify(mAms.mUsageStatsService).reportEvent(eq(PACKAGE_YELLOW), eq(UserHandle.USER_SYSTEM),
+ eq(Event.APP_COMPONENT_USED));
}
/**
@@ -520,7 +689,7 @@
@Test
public void testWedged() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
- final ProcessRecord receiverApp = makeActiveProcessRecordWedged(PACKAGE_GREEN);
+ final ProcessRecord receiverApp = makeWedgedActiveProcessRecord(PACKAGE_GREEN);
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
@@ -529,4 +698,250 @@
waitForIdle();
verify(mAms).appNotResponding(eq(receiverApp), any());
}
+
+ /**
+ * Verify that we cleanup a disabled component, skipping a pending dispatch
+ * of broadcast to that component.
+ */
+ @Test
+ public void testCleanup() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ try (SyncBarrier b = new SyncBarrier()) {
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>(
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_RED),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_BLUE)))));
+
+ synchronized (mAms) {
+ mQueue.cleanupDisabledPackageReceiversLocked(PACKAGE_GREEN, Set.of(CLASS_GREEN),
+ UserHandle.USER_SYSTEM);
+
+ // Also try clearing out other unrelated things that should leave
+ // the final receiver intact
+ mQueue.cleanupDisabledPackageReceiversLocked(PACKAGE_RED, null,
+ UserHandle.USER_SYSTEM);
+ mQueue.cleanupDisabledPackageReceiversLocked(null, null, USER_GUEST);
+ }
+
+ // To maximize test coverage, dump current state; we're not worried
+ // about the actual output, just that we don't crash
+ mQueue.dumpLocked(FileDescriptor.err, new PrintWriter(new ByteArrayOutputStream()),
+ null, 0, true, null, false);
+ }
+
+ waitForIdle();
+ verifyScheduleReceiver(times(1), receiverApp, airplane,
+ new ComponentName(PACKAGE_GREEN, CLASS_RED));
+ verifyScheduleReceiver(never(), receiverApp, airplane,
+ new ComponentName(PACKAGE_GREEN, CLASS_GREEN));
+ verifyScheduleReceiver(times(1), receiverApp, airplane,
+ new ComponentName(PACKAGE_GREEN, CLASS_BLUE));
+ }
+
+ /**
+ * Verify that we skip broadcasts to an app being backed up.
+ */
+ @Test
+ public void testBackup() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ receiverApp.setInFullBackup(true);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
+
+ waitForIdle();
+ verifyScheduleReceiver(never(), receiverApp, airplane,
+ new ComponentName(PACKAGE_GREEN, CLASS_GREEN));
+ }
+
+ /**
+ * Verify that an ordered broadcast collects results from everyone along the
+ * chain, and is delivered to final destination.
+ */
+ @Test
+ public void testOrdered() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+ // Purposefully warm-start the middle apps to make sure we dispatch to
+ // both cold and warm apps in expected order
+ makeActiveProcessRecord(makeApplicationInfo(PACKAGE_BLUE), PACKAGE_BLUE,
+ false, false, (extras) -> {
+ extras = clone(extras);
+ extras.putBoolean(PACKAGE_BLUE, true);
+ return extras;
+ });
+ makeActiveProcessRecord(makeApplicationInfo(PACKAGE_YELLOW), PACKAGE_YELLOW,
+ false, false, (extras) -> {
+ extras = clone(extras);
+ extras.putBoolean(PACKAGE_YELLOW, true);
+ return extras;
+ });
+
+ final IIntentReceiver orderedResultTo = mock(IIntentReceiver.class);
+ final Bundle orderedExtras = new Bundle();
+ orderedExtras.putBoolean(PACKAGE_RED, true);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeOrderedBroadcastRecord(airplane, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+ makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW)),
+ orderedResultTo, orderedExtras));
+
+ waitForIdle();
+ final IApplicationThread greenThread = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN)).getThread();
+ final IApplicationThread blueThread = mAms.getProcessRecordLocked(PACKAGE_BLUE,
+ getUidForPackage(PACKAGE_BLUE)).getThread();
+ final IApplicationThread yellowThread = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+ getUidForPackage(PACKAGE_YELLOW)).getThread();
+ final IApplicationThread redThread = mAms.getProcessRecordLocked(PACKAGE_RED,
+ getUidForPackage(PACKAGE_RED)).getThread();
+
+ // Verify that we called everyone in specific order, and that each of
+ // them observed the expected extras at that stage
+ final InOrder inOrder = inOrder(greenThread, blueThread, yellowThread, redThread);
+ final Bundle expectedExtras = new Bundle();
+ expectedExtras.putBoolean(PACKAGE_RED, true);
+ inOrder.verify(greenThread).scheduleReceiver(
+ argThat(filterEqualsIgnoringComponent(airplane)), any(), any(),
+ eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(true),
+ eq(UserHandle.USER_SYSTEM), anyInt());
+ inOrder.verify(blueThread).scheduleReceiver(
+ argThat(filterEqualsIgnoringComponent(airplane)), any(), any(),
+ eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(true),
+ eq(UserHandle.USER_SYSTEM), anyInt());
+ expectedExtras.putBoolean(PACKAGE_BLUE, true);
+ inOrder.verify(yellowThread).scheduleReceiver(
+ argThat(filterEqualsIgnoringComponent(airplane)), any(), any(),
+ eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(true),
+ eq(UserHandle.USER_SYSTEM), anyInt());
+ expectedExtras.putBoolean(PACKAGE_YELLOW, true);
+ inOrder.verify(redThread).scheduleRegisteredReceiver(any(), argThat(filterEquals(airplane)),
+ eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(false),
+ anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
+
+ // Finally, verify that we thawed the final receiver
+ verify(mAms.mOomAdjuster.mCachedAppOptimizer).unfreezeTemporarily(eq(callerApp),
+ eq(OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER));
+ }
+
+ /**
+ * Verify that an ordered broadcast can be aborted partially through
+ * dispatch, and is then delivered to final destination.
+ */
+ @Test
+ public void testOrdered_Aborting() throws Exception {
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ doOrdered_Aborting(airplane);
+ }
+
+ /**
+ * Verify that an ordered broadcast marked with
+ * {@link Intent#FLAG_RECEIVER_NO_ABORT} cannot be aborted partially through
+ * dispatch, and is delivered to everyone in order.
+ */
+ @Test
+ public void testOrdered_Aborting_NoAbort() throws Exception {
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ airplane.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
+ doOrdered_Aborting(airplane);
+ }
+
+ public void doOrdered_Aborting(@NonNull Intent intent) throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+ // Create a process that aborts any ordered broadcasts
+ makeActiveProcessRecord(makeApplicationInfo(PACKAGE_GREEN), PACKAGE_GREEN,
+ false, true, (extras) -> {
+ extras = clone(extras);
+ extras.putBoolean(PACKAGE_GREEN, true);
+ return extras;
+ });
+ makeActiveProcessRecord(PACKAGE_BLUE);
+
+ final IIntentReceiver orderedResultTo = mock(IIntentReceiver.class);
+
+ enqueueBroadcast(makeOrderedBroadcastRecord(intent, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE)),
+ orderedResultTo, null));
+
+ waitForIdle();
+ final IApplicationThread greenThread = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN)).getThread();
+ final IApplicationThread blueThread = mAms.getProcessRecordLocked(PACKAGE_BLUE,
+ getUidForPackage(PACKAGE_BLUE)).getThread();
+ final IApplicationThread redThread = mAms.getProcessRecordLocked(PACKAGE_RED,
+ getUidForPackage(PACKAGE_RED)).getThread();
+
+ final Bundle expectedExtras = new Bundle();
+ expectedExtras.putBoolean(PACKAGE_GREEN, true);
+
+ // Verify that we always invoke the first receiver, but then we might
+ // have invoked or skipped the second receiver depending on the intent
+ // flag policy; we always deliver to final receiver regardless of abort
+ final InOrder inOrder = inOrder(greenThread, blueThread, redThread);
+ inOrder.verify(greenThread).scheduleReceiver(
+ argThat(filterEqualsIgnoringComponent(intent)), any(), any(),
+ eq(Activity.RESULT_OK), any(), any(), eq(true), eq(UserHandle.USER_SYSTEM),
+ anyInt());
+ if ((intent.getFlags() & Intent.FLAG_RECEIVER_NO_ABORT) != 0) {
+ inOrder.verify(blueThread).scheduleReceiver(
+ argThat(filterEqualsIgnoringComponent(intent)), any(), any(),
+ eq(Activity.RESULT_OK), any(), any(), eq(true), eq(UserHandle.USER_SYSTEM),
+ anyInt());
+ } else {
+ inOrder.verify(blueThread, never()).scheduleReceiver(any(), any(), any(), anyInt(),
+ any(), any(), anyBoolean(), anyInt(), anyInt());
+ }
+ inOrder.verify(redThread).scheduleRegisteredReceiver(any(), argThat(filterEquals(intent)),
+ eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)),
+ eq(false), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
+ }
+
+ @Test
+ public void testBackgroundActivityStarts() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+ final Binder backgroundActivityStartsToken = new Binder();
+ final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ final BroadcastRecord r = new BroadcastRecord(mQueue, intent, callerApp,
+ callerApp.info.packageName, null, callerApp.getPid(), callerApp.info.uid, false,
+ null, null, null, null, AppOpsManager.OP_NONE, BroadcastOptions.makeBasic(),
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), null, Activity.RESULT_OK,
+ null, null, false, false, false, UserHandle.USER_SYSTEM, true,
+ backgroundActivityStartsToken, false, null);
+ enqueueBroadcast(r);
+
+ waitForIdle();
+ verify(receiverApp).addOrUpdateAllowBackgroundActivityStartsToken(eq(r),
+ eq(backgroundActivityStartsToken));
+ verify(receiverApp).removeAllowBackgroundActivityStartsToken(eq(r));
+ }
+
+ @Test
+ public void testOptions_TemporaryAppAllowlist() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setTemporaryAppAllowlist(1_000,
+ PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+ PowerExemptionManager.REASON_VPN, TAG);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, options,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
+
+ waitForIdle();
+ verify(mAms).tempAllowlistUidLocked(eq(receiverApp.uid), eq(1_000L),
+ eq(options.getTemporaryAppAllowlistReasonCode()), any(),
+ eq(options.getTemporaryAppAllowlistType()), eq(callerApp.uid));
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
new file mode 100644
index 0000000..5dc1251
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import static android.app.AppOpsManager.OP_COARSE_LOCATION;
+import static android.app.AppOpsManager.OP_FINE_LOCATION;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.quality.Strictness;
+
+public class AppOpsLegacyRestrictionsTest {
+ private static final int UID_ANY = -2;
+
+ final Object mClientToken = new Object();
+ final int mUserId1 = 65001;
+ final int mUserId2 = 65002;
+ final int mOpCode1 = OP_COARSE_LOCATION;
+ final int mOpCode2 = OP_FINE_LOCATION;
+ final String mPackageName = "com.example.test";
+ final String mAttributionTag = "test-attribution-tag";
+
+ StaticMockitoSession mSession;
+
+ @Mock
+ AppOpsService.Constants mConstants;
+
+ @Mock
+ Context mContext;
+
+ @Mock
+ Handler mHandler;
+
+ @Mock
+ AppOpsServiceInterface mLegacyAppOpsService;
+
+ AppOpsRestrictions mAppOpsRestrictions;
+
+ @Before
+ public void setUp() {
+ mSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ mConstants.TOP_STATE_SETTLE_TIME = 10 * 1000L;
+ mConstants.FG_SERVICE_STATE_SETTLE_TIME = 5 * 1000L;
+ mConstants.BG_STATE_SETTLE_TIME = 1 * 1000L;
+ Mockito.when(mHandler.post(Mockito.any(Runnable.class))).then(inv -> {
+ Runnable r = inv.getArgument(0);
+ r.run();
+ return true;
+ });
+ mAppOpsRestrictions = new AppOpsRestrictionsImpl(mContext, mHandler, mLegacyAppOpsService);
+ }
+
+ @After
+ public void tearDown() {
+ mSession.finishMocking();
+ }
+
+ @Test
+ public void testSetAndGetSingleGlobalRestriction() {
+ // Verify: empty
+ assertEquals(false, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+ assertEquals(false, mAppOpsRestrictions.getGlobalRestriction(mClientToken, mOpCode1));
+ // Act: add a restriction
+ assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, true));
+ // Act: add same restriction again (expect false; should be no-op)
+ assertEquals(false, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, true));
+ // Verify: not empty
+ assertEquals(true, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+ assertEquals(true, mAppOpsRestrictions.getGlobalRestriction(mClientToken, mOpCode1));
+ // Act: remove the restriction
+ assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, false));
+ // Act: remove same restriction again (expect false; should be no-op)
+ assertEquals(false,
+ mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, false));
+ // Verify: empty
+ assertEquals(false, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+ assertEquals(false, mAppOpsRestrictions.getGlobalRestriction(mClientToken, mOpCode1));
+ }
+
+ @Test
+ public void testSetAndGetDoubleGlobalRestriction() {
+ // Act: add opCode1 restriction
+ assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, true));
+ // Act: add opCode2 restriction
+ assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode2, true));
+ // Verify: not empty
+ assertEquals(true, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+ // Act: remove opCode1 restriction
+ assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, false));
+ // Verify: not empty
+ assertEquals(true, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+ // Act: remove opCode2 restriction
+ assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode2, false));
+ // Verify: empty
+ assertEquals(false, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+ }
+
+ @Test
+ public void testClearGlobalRestrictions() {
+ // Act: clear (should be no-op)
+ assertEquals(false, mAppOpsRestrictions.clearGlobalRestrictions(mClientToken));
+ // Act: add opCodes
+ assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, true));
+ assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode2, true));
+ // Verify: not empty
+ assertEquals(true, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+ // Act: clear
+ assertEquals(true, mAppOpsRestrictions.clearGlobalRestrictions(mClientToken));
+ // Verify: empty
+ assertEquals(false, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+ // Act: clear (should be no-op)
+ assertEquals(false, mAppOpsRestrictions.clearGlobalRestrictions(mClientToken));
+ }
+
+ @Test
+ public void testSetAndGetSingleUserRestriction() {
+ // Verify: empty
+ assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
+ assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, true));
+ // Act: add a restriction
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode1, true, null));
+ // Act: add the restriction again (should be no-op)
+ assertEquals(false, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode1, true, null));
+ // Verify: not empty
+ assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
+ assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, true));
+ // Act: remove the restriction
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode1, false, null));
+ // Act: remove the restriction again (should be no-op)
+ assertEquals(false, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode1, false, null));
+ // Verify: empty
+ assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
+ }
+
+ @Test
+ public void testSetAndGetDoubleUserRestriction() {
+ // Act: add opCode1 restriction
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode1, true, null));
+ // Act: add opCode2 restriction
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode2, true, null));
+ // Verify: not empty
+ assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
+ assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, true));
+ assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, false));
+ assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, true));
+ // Act: remove opCode1 restriction
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode1, false, null));
+ // Verify: opCode1 is removed but not opCode22
+ assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
+ assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, true));
+ assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, false));
+ assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, true));
+ // Act: remove opCode2 restriction
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode2, false, null));
+ // Verify: empty
+ assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, false));
+ assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, true));
+ assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ }
+
+ @Test
+ public void testClearUserRestrictionsAllUsers() {
+ // Act: clear (should be no-op)
+ assertEquals(false, mAppOpsRestrictions.clearUserRestrictions(mClientToken));
+ // Act: add restrictions
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode1, true, null));
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode2, true, null));
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId2, mOpCode1, true, null));
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId2, mOpCode2, true, null));
+ // Verify: not empty
+ assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ // Act: clear all user restrictions
+ assertEquals(true, mAppOpsRestrictions.clearUserRestrictions(mClientToken));
+ // Verify: empty
+ assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ }
+
+ @Test
+ public void testClearUserRestrictionsSpecificUsers() {
+ // Act: clear (should be no-op)
+ assertEquals(false, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId1));
+ // Act: add restrictions
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode1, true, null));
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode2, true, null));
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId2, mOpCode1, true, null));
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId2, mOpCode2, true, null));
+ // Verify: not empty
+ assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ // Act: clear userId1
+ assertEquals(true, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId1));
+ // Act: clear userId1 again (should be no-op)
+ assertEquals(false, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId1));
+ // Verify: userId1 is removed but not userId2
+ assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
+ assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId2, mOpCode2, mPackageName, mAttributionTag, false));
+ // Act: clear userId2
+ assertEquals(true, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId2));
+ // Act: clear userId2 again (should be no-op)
+ assertEquals(false, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId2));
+ // Verify: empty
+ assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ }
+
+ @Test
+ public void testNotify() {
+ mAppOpsRestrictions.setUserRestriction(mClientToken, mUserId1, mOpCode1, true, null);
+ mAppOpsRestrictions.clearUserRestrictions(mClientToken);
+ Mockito.verify(mLegacyAppOpsService, Mockito.times(1))
+ .notifyWatchersOfChange(mOpCode1, UID_ANY);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
index 8744f32..e28d331 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
@@ -386,7 +386,7 @@
pkg.setTargetSdkVersion(Build.VERSION_CODES.S)
libraries?.forEach { pkg.addLibraryName(it) }
staticLibrary?.let {
- pkg.setStaticSharedLibName(it)
+ pkg.setStaticSharedLibraryName(it)
pkg.setStaticSharedLibVersion(staticLibraryVersion)
pkg.setStaticSharedLibrary(true)
}
@@ -430,7 +430,7 @@
setTargetSdkVersion(Build.VERSION_CODES.S)
libraries?.forEach { addLibraryName(it) }
staticLibrary?.let {
- setStaticSharedLibName(it)
+ setStaticSharedLibraryName(it)
setStaticSharedLibVersion(staticLibraryVersion)
setStaticSharedLibrary(true)
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java
index 245b4dc..278e04a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java
@@ -19,8 +19,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static com.android.server.pm.UserManagerInternal.PARENT_DISPLAY;
-
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
@@ -185,10 +183,11 @@
addDefaultProfileAndParent();
mUmi.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- mUmi.assignUserToDisplay(PROFILE_USER_ID, PARENT_DISPLAY);
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
- assertUsersAssignedToDisplays(PARENT_USER_ID, SECONDARY_DISPLAY_ID,
- pair(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+ Log.v(TAG, "Exception: " + e);
+ assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
}
@Test
@@ -198,7 +197,20 @@
mUmi.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+ () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, OTHER_SECONDARY_DISPLAY_ID));
+
+ Log.v(TAG, "Exception: " + e);
+ assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testAssignUserToDisplay_profileDefaultDisplayParentOnSecondaryDisplay() {
+ enableUsersOnSecondaryDisplays();
+ addDefaultProfileAndParent();
+
+ mUmi.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY));
Log.v(TAG, "Exception: " + e);
assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
index 6f0efb0..90a5fa0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
@@ -33,7 +33,6 @@
import android.content.pm.UserInfo;
import android.os.UserManager;
import android.util.Log;
-import android.util.Pair;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -614,24 +613,6 @@
.containsExactly(userId, displayId);
}
- @SafeVarargs
- protected final void assertUsersAssignedToDisplays(@UserIdInt int userId, int displayId,
- @SuppressWarnings("unchecked") Pair<Integer, Integer>... others) {
- Object[] otherObjects = new Object[others.length * 2];
- for (int i = 0; i < others.length; i++) {
- Pair<Integer, Integer> other = others[i];
- otherObjects[i * 2] = other.first;
- otherObjects[i * 2 + 1] = other.second;
-
- }
- assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap())
- .containsExactly(userId, displayId, otherObjects);
- }
-
- protected static Pair<Integer, Integer> pair(@UserIdInt int userId, int secondaryDisplayId) {
- return new Pair<>(userId, secondaryDisplayId);
- }
-
///////////////////
// Private infra //
///////////////////
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 81f899c..96c3823 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -679,7 +679,7 @@
setUpAndStartProfileInBackground(TEST_USER_ID1);
startBackgroundUserAssertions();
- verifyUserAssignedToDisplay(TEST_USER_ID1, UserManagerInternal.PARENT_DISPLAY);
+ verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 67eeb4e..68310f4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -1011,10 +1011,10 @@
.addUsesPermission(new ParsedUsesPermissionImpl("foo7", 0))
.addImplicitPermission("foo25")
.addProtectedBroadcast("foo8")
- .setSdkLibName("sdk12")
+ .setSdkLibraryName("sdk12")
.setSdkLibVersionMajor(42)
.addUsesSdkLibrary("sdk23", 200, new String[]{"digest2"})
- .setStaticSharedLibName("foo23")
+ .setStaticSharedLibraryName("foo23")
.setStaticSharedLibVersion(100)
.addUsesStaticLibrary("foo23", 100, new String[]{"digest"})
.addLibraryName("foo10")
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index 084f4f1..6f3249e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -241,7 +241,7 @@
@Test
public void installSdkLibrary() throws Exception {
final ParsedPackage pkg = ((ParsedPackage) createBasicPackage("ogl.sdk_123")
- .setSdkLibName("ogl.sdk")
+ .setSdkLibraryName("ogl.sdk")
.setSdkLibVersionMajor(123)
.hideAsParsed())
.setPackageName("ogl.sdk_123")
@@ -272,7 +272,7 @@
@Test
public void installStaticSharedLibrary() throws Exception {
final ParsedPackage pkg = ((ParsedPackage) createBasicPackage("static.lib.pkg")
- .setStaticSharedLibName("static.lib")
+ .setStaticSharedLibraryName("static.lib")
.setStaticSharedLibVersion(123L)
.hideAsParsed())
.setPackageName("static.lib.pkg.123")
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
new file mode 100644
index 0000000..ad47773
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import static android.view.KeyEvent.KEYCODE_A;
+import static android.view.KeyEvent.KEYCODE_ALT_LEFT;
+import static android.view.KeyEvent.KEYCODE_B;
+import static android.view.KeyEvent.KEYCODE_C;
+import static android.view.KeyEvent.KEYCODE_CTRL_LEFT;
+import static android.view.KeyEvent.KEYCODE_E;
+import static android.view.KeyEvent.KEYCODE_L;
+import static android.view.KeyEvent.KEYCODE_M;
+import static android.view.KeyEvent.KEYCODE_META_LEFT;
+import static android.view.KeyEvent.KEYCODE_N;
+import static android.view.KeyEvent.KEYCODE_P;
+import static android.view.KeyEvent.KEYCODE_S;
+import static android.view.KeyEvent.KEYCODE_SLASH;
+import static android.view.KeyEvent.KEYCODE_SPACE;
+import static android.view.KeyEvent.KEYCODE_TAB;
+import static android.view.KeyEvent.KEYCODE_Z;
+
+import android.content.Intent;
+import android.os.RemoteException;
+import android.util.SparseArray;
+
+import org.junit.Test;
+
+public class ModifierShortcutTests extends ShortcutKeyTestBase {
+ private static final SparseArray<String> META_SHORTCUTS = new SparseArray<>();
+ static {
+ META_SHORTCUTS.append(KEYCODE_A, Intent.CATEGORY_APP_CALCULATOR);
+ META_SHORTCUTS.append(KEYCODE_B, Intent.CATEGORY_APP_BROWSER);
+ META_SHORTCUTS.append(KEYCODE_C, Intent.CATEGORY_APP_CONTACTS);
+ META_SHORTCUTS.append(KEYCODE_E, Intent.CATEGORY_APP_EMAIL);
+ META_SHORTCUTS.append(KEYCODE_L, Intent.CATEGORY_APP_CALENDAR);
+ META_SHORTCUTS.append(KEYCODE_M, Intent.CATEGORY_APP_MAPS);
+ META_SHORTCUTS.append(KEYCODE_P, Intent.CATEGORY_APP_MUSIC);
+ META_SHORTCUTS.append(KEYCODE_S, Intent.CATEGORY_APP_MESSAGING);
+ }
+
+ /**
+ * Test meta+ shortcuts defined in bookmarks.xml.
+ */
+ @Test
+ public void testMetaShortcuts() {
+ for (int i = 0; i < META_SHORTCUTS.size(); i++) {
+ final int keyCode = META_SHORTCUTS.keyAt(i);
+ final String category = META_SHORTCUTS.valueAt(i);
+
+ sendKeyCombination(new int[]{KEYCODE_META_LEFT, keyCode}, 0);
+ mPhoneWindowManager.assertLaunchCategory(category);
+ }
+ }
+
+ /**
+ * ALT + TAB to show recent apps.
+ */
+ @Test
+ public void testAltTab() {
+ mPhoneWindowManager.overrideStatusBarManagerInternal();
+ sendKeyCombination(new int[]{KEYCODE_ALT_LEFT, KEYCODE_TAB}, 0);
+ mPhoneWindowManager.assertShowRecentApps();
+ }
+
+ /**
+ * CTRL + SPACE to switch keyboard layout.
+ */
+ @Test
+ public void testCtrlSpace() {
+ sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SPACE}, 0);
+ mPhoneWindowManager.assertSwitchKeyboardLayout();
+ }
+
+ /**
+ * META + SPACE to switch keyboard layout.
+ */
+ @Test
+ public void testMetaSpace() {
+ sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SPACE}, 0);
+ mPhoneWindowManager.assertSwitchKeyboardLayout();
+ }
+
+ /**
+ * CTRL + ALT + Z to enable accessibility service.
+ */
+ @Test
+ public void testCtrlAltZ() {
+ sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_ALT_LEFT, KEYCODE_Z}, 0);
+ mPhoneWindowManager.assertAccessibilityKeychordCalled();
+ }
+
+ /**
+ * META + CTRL+ S to take screenshot.
+ */
+ @Test
+ public void testMetaCtrlS() {
+ sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_CTRL_LEFT, KEYCODE_S}, 0);
+ mPhoneWindowManager.assertTakeScreenshotCalled();
+ }
+
+ /**
+ * META + N to expand notification panel.
+ */
+ @Test
+ public void testMetaN() throws RemoteException {
+ mPhoneWindowManager.overrideExpandNotificationsPanel();
+ sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_N}, 0);
+ mPhoneWindowManager.assertExpandNotification();
+ }
+
+ /**
+ * META + SLASH to toggle shortcuts menu.
+ */
+ @Test
+ public void testMetaSlash() {
+ mPhoneWindowManager.overrideStatusBarManagerInternal();
+ sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SLASH}, 0);
+ mPhoneWindowManager.assertToggleShortcutsMenu();
+ }
+
+ /**
+ * META + ALT to toggle Cap Lock.
+ */
+ @Test
+ public void testMetaAlt() {
+ sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_ALT_LEFT}, 0);
+ mPhoneWindowManager.assertToggleCapsLock();
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index ee11ac8..fe46c14 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -32,6 +32,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_ASSISTANT;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GLOBAL_ACTIONS;
@@ -53,6 +54,7 @@
import android.app.NotificationManager;
import android.app.SearchManager;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
@@ -62,6 +64,7 @@
import android.os.HandlerThread;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
+import android.os.RemoteException;
import android.os.Vibrator;
import android.service.dreams.DreamManagerInternal;
import android.telecom.TelecomManager;
@@ -73,12 +76,16 @@
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.server.GestureLauncherService;
import com.android.server.LocalServices;
+import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.vr.VrManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.DisplayPolicy;
import com.android.server.wm.DisplayRotation;
import com.android.server.wm.WindowManagerInternal;
+import junit.framework.Assert;
+
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockSettings;
import org.mockito.Mockito;
@@ -118,6 +125,8 @@
@Mock private GlobalActions mGlobalActions;
@Mock private AccessibilityShortcutController mAccessibilityShortcutController;
+ @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
+
private StaticMockitoSession mMockitoSession;
private HandlerThread mHandlerThread;
private Handler mHandler;
@@ -226,6 +235,8 @@
mPhoneWindowManager.systemBooted();
overrideLaunchAccessibility();
+ doReturn(false).when(mPhoneWindowManager).keyguardOn();
+ doNothing().when(mContext).startActivityAsUser(any(), any());
}
void tearDown() {
@@ -310,6 +321,22 @@
doReturn(true).when(mTelecomManager).endCall();
}
+ void overrideExpandNotificationsPanel() {
+ // Can't directly mock on IStatusbarService, use spyOn and override the specific api.
+ mPhoneWindowManager.getStatusBarService();
+ spyOn(mPhoneWindowManager.mStatusBarService);
+ try {
+ doNothing().when(mPhoneWindowManager.mStatusBarService).expandNotificationsPanel();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ void overrideStatusBarManagerInternal() {
+ doReturn(mStatusBarManagerInternal).when(
+ () -> LocalServices.getService(eq(StatusBarManagerInternal.class)));
+ }
+
/**
* Below functions will check the policy behavior could be invoked.
*/
@@ -368,4 +395,46 @@
waitForIdle();
verify(mSearchManager, timeout(SHORTCUT_KEY_DELAY_MILLIS)).launchAssist(any());
}
+
+ void assertLaunchCategory(String category) {
+ waitForIdle();
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext).startActivityAsUser(intentCaptor.capture(), any());
+ Assert.assertTrue(intentCaptor.getValue().getSelector().hasCategory(category));
+ // Reset verifier for next call.
+ Mockito.reset(mContext);
+ }
+
+ void assertShowRecentApps() {
+ waitForIdle();
+ verify(mStatusBarManagerInternal).showRecentApps(anyBoolean());
+ }
+
+ void assertSwitchKeyboardLayout() {
+ waitForIdle();
+ verify(mWindowManagerFuncsImpl).switchKeyboardLayout(anyInt(), anyInt());
+ }
+
+ void assertTakeBugreport() {
+ waitForIdle();
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext).sendOrderedBroadcastAsUser(intentCaptor.capture(), any(), any(), any(),
+ any(), anyInt(), any(), any());
+ Assert.assertTrue(intentCaptor.getValue().getAction() == Intent.ACTION_BUG_REPORT);
+ }
+
+ void assertExpandNotification() throws RemoteException {
+ waitForIdle();
+ verify(mPhoneWindowManager.mStatusBarService).expandNotificationsPanel();
+ }
+
+ void assertToggleShortcutsMenu() {
+ waitForIdle();
+ verify(mStatusBarManagerInternal).toggleKeyboardShortcutsMenu(anyInt());
+ }
+
+ void assertToggleCapsLock() {
+ waitForIdle();
+ verify(mInputManagerInternal).toggleCapsLock(anyInt());
+ }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 4d801c90..8d4da8a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -24,6 +24,7 @@
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.Rect
import com.android.server.wm.traces.common.WindowManagerConditionsFactory
+import com.android.server.wm.traces.common.region.Region
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
@@ -178,6 +179,20 @@
wmHelper.StateSyncBuilder()
.withAppTransitionIdle()
.waitForAndVerify()
+ waitForPipWindowToExpandFrom(wmHelper, Region.from(windowRect))
+ }
+
+ private fun waitForPipWindowToExpandFrom(
+ wmHelper: WindowManagerStateHelper,
+ windowRect: Region
+ ) {
+ wmHelper.StateSyncBuilder().add("pipWindowExpanded") {
+ val pipAppWindow = it.wmState.visibleWindows.firstOrNull { window ->
+ this.windowMatchesAnyOf(window)
+ } ?: return@add false
+ val pipRegion = pipAppWindow.frameRegion
+ return@add pipRegion.coversMoreThan(windowRect)
+ }.waitForAndVerify()
}
companion object {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
index 77f28f6..2babf1c8e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
@@ -19,6 +19,7 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.RequiresDevice
+import android.view.Surface
import android.view.WindowInsets
import android.view.WindowManager
import androidx.test.uiautomator.By
@@ -74,6 +75,13 @@
.withFullScreenApp(testApp)
.waitForAndVerify()
testApp.postNotification(wmHelper)
+
+ if (testSpec.isTablet) {
+ tapl.setExpectedRotation(testSpec.startRotation)
+ } else {
+ tapl.setExpectedRotation(Surface.ROTATION_0)
+ }
+
tapl.goHome()
wmHelper.StateSyncBuilder()
.withHomeActivityVisible()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
index 4f21412..d362c7d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
@@ -31,11 +31,13 @@
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.WindowManagerConditionsFactory
+import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -86,6 +88,7 @@
@Presubmit
@Test
fun testSimpleActivityIsShownDirectly() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
testSpec.assertLayers {
isVisible(ComponentNameMatcher.LAUNCHER)
.isInvisible(ComponentNameMatcher.SPLASH_SCREEN)
diff --git a/tests/benchmarks/internal/src/com/android/internal/LambdaPerfTest.java b/tests/benchmarks/internal/src/com/android/internal/LambdaPerfTest.java
index 3885486..2001c04 100644
--- a/tests/benchmarks/internal/src/com/android/internal/LambdaPerfTest.java
+++ b/tests/benchmarks/internal/src/com/android/internal/LambdaPerfTest.java
@@ -1,454 +1,458 @@
-/*
- * 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 com.android.internal;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import android.app.Activity;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.SystemClock;
-import android.util.Log;
-
-import androidx.test.filters.LargeTest;
-
-import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.internal.util.function.pooled.PooledPredicate;
-
-import org.junit.Assume;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runners.model.Statement;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/** Compares the performance of regular lambda and pooled lambda. */
-@LargeTest
-public class LambdaPerfTest {
- private static final boolean DEBUG = false;
- private static final String TAG = LambdaPerfTest.class.getSimpleName();
-
- private static final String LAMBDA_FORM_REGULAR = "regular";
- private static final String LAMBDA_FORM_POOLED = "pooled";
-
- private static final int WARMUP_ITERATIONS = 1000;
- private static final int TEST_ITERATIONS = 3000000;
- private static final int TASK_COUNT = 10;
- private static final long DELAY_AFTER_BENCH_MS = 1000;
-
- private String mMethodName;
-
- private final Bundle mTestResults = new Bundle();
- private final ArrayList<Task> mTasks = new ArrayList<>();
-
- // The member fields are used to ensure lambda capturing. They don't have the actual meaning.
- private final Task mTask = new Task();
- private final Rect mBounds = new Rect();
- private int mTaskId;
- private long mTime;
- private boolean mTop;
-
- @Rule
- public final TestRule mRule = (base, description) -> new Statement() {
- @Override
- public void evaluate() throws Throwable {
- mMethodName = description.getMethodName();
- mTasks.clear();
- for (int i = 0; i < TASK_COUNT; i++) {
- final Task t = new Task();
- mTasks.add(t);
- }
- base.evaluate();
-
- getInstrumentation().sendStatus(Activity.RESULT_OK, mTestResults);
- }
- };
-
- @Test
- public void test1ParamConsumer() {
- evaluate(LAMBDA_FORM_REGULAR, () -> forAllTask(t -> t.doSomething(mTask)));
- evaluate(LAMBDA_FORM_POOLED, () -> {
- final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,
- PooledLambda.__(Task.class), mTask);
- forAllTask(c);
- c.recycle();
- });
- }
-
- @Test
- public void test2PrimitiveParamsConsumer() {
- // Not in Integer#IntegerCache (-128~127) for autoboxing, that will create new object.
- mTaskId = 12345;
- mTime = 54321;
-
- evaluate(LAMBDA_FORM_REGULAR, () -> forAllTask(t -> t.doSomething(mTaskId, mTime)));
- evaluate(LAMBDA_FORM_POOLED, () -> {
- final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,
- PooledLambda.__(Task.class), mTaskId, mTime);
- forAllTask(c);
- c.recycle();
- });
- }
-
- @Test
- public void test3ParamsPredicate() {
- mTop = true;
- // In Integer#IntegerCache.
- mTaskId = 10;
-
- evaluate(LAMBDA_FORM_REGULAR, () -> handleTask(t -> t.doSomething(mBounds, mTop, mTaskId)));
- evaluate(LAMBDA_FORM_POOLED, () -> {
- final PooledPredicate c = PooledLambda.obtainPredicate(Task::doSomething,
- PooledLambda.__(Task.class), mBounds, mTop, mTaskId);
- handleTask(c);
- c.recycle();
- });
- }
-
- @Test
- public void testMessage() {
- evaluate(LAMBDA_FORM_REGULAR, () -> {
- final Message m = Message.obtain().setCallback(() -> mTask.doSomething(mTaskId, mTime));
- m.getCallback().run();
- m.recycle();
- });
- evaluate(LAMBDA_FORM_POOLED, () -> {
- final Message m = PooledLambda.obtainMessage(Task::doSomething, mTask, mTaskId, mTime);
- m.getCallback().run();
- m.recycle();
- });
- }
-
- @Test
- public void testRunnable() {
- evaluate(LAMBDA_FORM_REGULAR, () -> {
- final Runnable r = mTask::doSomething;
- r.run();
- });
- evaluate(LAMBDA_FORM_POOLED, () -> {
- final Runnable r = PooledLambda.obtainRunnable(Task::doSomething, mTask).recycleOnUse();
- r.run();
- });
- }
-
- @Test
- public void testMultiThread() {
- final int numThread = 3;
-
- final Runnable regularAction = () -> forAllTask(t -> t.doSomething(mTask));
- final Runnable[] regularActions = new Runnable[numThread];
- Arrays.fill(regularActions, regularAction);
- evaluateMultiThread(LAMBDA_FORM_REGULAR, regularActions);
-
- final Runnable pooledAction = () -> {
- final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,
- PooledLambda.__(Task.class), mTask);
- forAllTask(c);
- c.recycle();
- };
- final Runnable[] pooledActions = new Runnable[numThread];
- Arrays.fill(pooledActions, pooledAction);
- evaluateMultiThread(LAMBDA_FORM_POOLED, pooledActions);
- }
-
- private void forAllTask(Consumer<Task> callback) {
- for (int i = mTasks.size() - 1; i >= 0; i--) {
- callback.accept(mTasks.get(i));
- }
- }
-
- private void handleTask(Predicate<Task> callback) {
- for (int i = mTasks.size() - 1; i >= 0; i--) {
- final Task task = mTasks.get(i);
- if (callback.test(task)) {
- return;
- }
- }
- }
-
- private void evaluate(String title, Runnable action) {
- for (int i = 0; i < WARMUP_ITERATIONS; i++) {
- action.run();
- }
- performGc();
-
- final GcStatus startGcStatus = getGcStatus();
- final long startTime = SystemClock.elapsedRealtime();
- for (int i = 0; i < TEST_ITERATIONS; i++) {
- action.run();
- }
- evaluateResult(title, startGcStatus, startTime);
- }
-
- private void evaluateMultiThread(String title, Runnable[] actions) {
- performGc();
-
- final CountDownLatch latch = new CountDownLatch(actions.length);
- final GcStatus startGcStatus = getGcStatus();
- final long startTime = SystemClock.elapsedRealtime();
- for (Runnable action : actions) {
- new Thread() {
- @Override
- public void run() {
- for (int i = 0; i < TEST_ITERATIONS; i++) {
- action.run();
- }
- latch.countDown();
- };
- }.start();
- }
- try {
- latch.await();
- } catch (InterruptedException ignored) {
- }
- evaluateResult(title, startGcStatus, startTime);
- }
-
- private void evaluateResult(String title, GcStatus startStatus, long startTime) {
- final float elapsed = SystemClock.elapsedRealtime() - startTime;
- // Sleep a while to see if GC may happen.
- SystemClock.sleep(DELAY_AFTER_BENCH_MS);
- final GcStatus endStatus = getGcStatus();
- final GcInfo info = startStatus.calculateGcTime(endStatus, title, mTestResults);
- Log.i(TAG, mMethodName + "_" + title + " execution time: "
- + elapsed + "ms (avg=" + String.format("%.5f", elapsed / TEST_ITERATIONS) + "ms)"
- + " GC time: " + String.format("%.3f", info.mTotalGcTime) + "ms"
- + " GC paused time: " + String.format("%.3f", info.mTotalGcPausedTime) + "ms");
- }
-
- /** Cleans the test environment. */
- private static void performGc() {
- System.gc();
- System.runFinalization();
- System.gc();
- }
-
- private static GcStatus getGcStatus() {
- if (DEBUG) {
- Log.i(TAG, "===== Read GC dump =====");
- }
- final GcStatus status = new GcStatus();
- final List<String> vmDump = getVmDump();
- Assume.assumeFalse("VM dump is empty", vmDump.isEmpty());
- for (String line : vmDump) {
- status.visit(line);
- if (line.startsWith("DALVIK THREADS")) {
- break;
- }
- }
- return status;
- }
-
- private static List<String> getVmDump() {
- final int myPid = Process.myPid();
- // Another approach Debug#dumpJavaBacktraceToFileTimeout requires setenforce 0.
- Process.sendSignal(myPid, Process.SIGNAL_QUIT);
- // Give a chance to handle the signal.
- SystemClock.sleep(100);
-
- String dump = null;
- final String pattern = myPid + " written to: ";
- final List<String> logs = shell("logcat -v brief -d tombstoned:I *:S");
- for (int i = logs.size() - 1; i >= 0; i--) {
- final String log = logs.get(i);
- // Log pattern: Traces for pid 9717 written to: /data/anr/trace_07
- final int pos = log.indexOf(pattern);
- if (pos > 0) {
- dump = log.substring(pattern.length() + pos);
- break;
- }
- }
-
- Assume.assumeNotNull("Unable to find VM dump", dump);
- // It requires system or root uid to read the trace.
- return shell("cat " + dump);
- }
-
- private static List<String> shell(String command) {
- final ParcelFileDescriptor.AutoCloseInputStream stream =
- new ParcelFileDescriptor.AutoCloseInputStream(
- getInstrumentation().getUiAutomation().executeShellCommand(command));
- final ArrayList<String> lines = new ArrayList<>();
- try (BufferedReader br = new BufferedReader(new InputStreamReader(stream))) {
- String line;
- while ((line = br.readLine()) != null) {
- lines.add(line);
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- return lines;
- }
-
- /** An empty class which provides some methods with different type arguments. */
- static class Task {
- void doSomething() {
- }
-
- void doSomething(Task t) {
- }
-
- void doSomething(int taskId, long time) {
- }
-
- boolean doSomething(Rect bounds, boolean top, int taskId) {
- return false;
- }
- }
-
- static class ValPattern {
- static final int TYPE_COUNT = 0;
- static final int TYPE_TIME = 1;
- static final String PATTERN_COUNT = "(\\d+)";
- static final String PATTERN_TIME = "(\\d+\\.?\\d+)(\\w+)";
- final String mRawPattern;
- final Pattern mPattern;
- final int mType;
-
- int mIntValue;
- float mFloatValue;
-
- ValPattern(String p, int type) {
- mRawPattern = p;
- mPattern = Pattern.compile(
- p + (type == TYPE_TIME ? PATTERN_TIME : PATTERN_COUNT) + ".*");
- mType = type;
- }
-
- boolean visit(String line) {
- final Matcher matcher = mPattern.matcher(line);
- if (!matcher.matches()) {
- return false;
- }
- final String value = matcher.group(1);
- if (value == null) {
- return false;
- }
- if (mType == TYPE_COUNT) {
- mIntValue = Integer.parseInt(value);
- return true;
- }
- final float time = Float.parseFloat(value);
- final String unit = matcher.group(2);
- if (unit == null) {
- return false;
- }
- // Refer to art/libartbase/base/time_utils.cc
- switch (unit) {
- case "s":
- mFloatValue = time * 1000;
- break;
- case "ms":
- mFloatValue = time;
- break;
- case "us":
- mFloatValue = time / 1000;
- break;
- case "ns":
- mFloatValue = time / 1000 / 1000;
- break;
- default:
- throw new IllegalArgumentException();
- }
-
- return true;
- }
-
- @Override
- public String toString() {
- return mRawPattern + (mType == TYPE_TIME ? (mFloatValue + "ms") : mIntValue);
- }
- }
-
- /** Parses the dump pattern of Heap::DumpGcPerformanceInfo. */
- private static class GcStatus {
- private static final int TOTAL_GC_TIME_INDEX = 1;
- private static final int TOTAL_GC_PAUSED_TIME_INDEX = 5;
-
- // Refer to art/runtime/gc/heap.cc
- final ValPattern[] mPatterns = {
- new ValPattern("Total GC count: ", ValPattern.TYPE_COUNT),
- new ValPattern("Total GC time: ", ValPattern.TYPE_TIME),
- new ValPattern("Total time waiting for GC to complete: ", ValPattern.TYPE_TIME),
- new ValPattern("Total blocking GC count: ", ValPattern.TYPE_COUNT),
- new ValPattern("Total blocking GC time: ", ValPattern.TYPE_TIME),
- new ValPattern("Total mutator paused time: ", ValPattern.TYPE_TIME),
- new ValPattern("Total number of allocations ", ValPattern.TYPE_COUNT),
- new ValPattern("concurrent copying paused: Sum: ", ValPattern.TYPE_TIME),
- new ValPattern("concurrent copying total time: ", ValPattern.TYPE_TIME),
- new ValPattern("concurrent copying freed: ", ValPattern.TYPE_COUNT),
- new ValPattern("Peak regions allocated ", ValPattern.TYPE_COUNT),
- };
-
- void visit(String dumpLine) {
- for (ValPattern p : mPatterns) {
- if (p.visit(dumpLine)) {
- if (DEBUG) {
- Log.i(TAG, " " + p);
- }
- }
- }
- }
-
- GcInfo calculateGcTime(GcStatus newStatus, String title, Bundle result) {
- Log.i(TAG, "===== GC status of " + title + " =====");
- final GcInfo info = new GcInfo();
- for (int i = 0; i < mPatterns.length; i++) {
- final ValPattern p = mPatterns[i];
- if (p.mType == ValPattern.TYPE_COUNT) {
- final int diff = newStatus.mPatterns[i].mIntValue - p.mIntValue;
- Log.i(TAG, " " + p.mRawPattern + diff);
- if (diff > 0) {
- result.putInt("[" + title + "] " + p.mRawPattern, diff);
- }
- continue;
- }
- final float diff = newStatus.mPatterns[i].mFloatValue - p.mFloatValue;
- Log.i(TAG, " " + p.mRawPattern + diff + "ms");
- if (diff > 0) {
- result.putFloat("[" + title + "] " + p.mRawPattern + "(ms)", diff);
- }
- if (i == TOTAL_GC_TIME_INDEX) {
- info.mTotalGcTime = diff;
- } else if (i == TOTAL_GC_PAUSED_TIME_INDEX) {
- info.mTotalGcPausedTime = diff;
- }
- }
- return info;
- }
- }
-
- private static class GcInfo {
- float mTotalGcTime;
- float mTotalGcPausedTime;
- }
-}
+/*
+ * 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 com.android.internal;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.test.filters.LargeTest;
+
+import com.android.internal.util.function.pooled.PooledConsumer;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.internal.util.function.pooled.PooledPredicate;
+
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runners.model.Statement;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Compares the performance of regular lambda and pooled lambda. */
+@LargeTest
+public class LambdaPerfTest {
+ private static final boolean DEBUG = false;
+ private static final String TAG = LambdaPerfTest.class.getSimpleName();
+
+ private static final String LAMBDA_FORM_REGULAR = "regular";
+ private static final String LAMBDA_FORM_POOLED = "pooled";
+
+ private static final int WARMUP_ITERATIONS = 1000;
+ private static final int TEST_ITERATIONS = 3000000;
+ private static final int TASK_COUNT = 10;
+ private static final long DELAY_AFTER_BENCH_MS = 1000;
+
+ private String mMethodName;
+
+ private final Bundle mTestResults = new Bundle();
+ private final ArrayList<Task> mTasks = new ArrayList<>();
+
+ // The member fields are used to ensure lambda capturing. They don't have the actual meaning.
+ private final Task mTask = new Task();
+ private final Rect mBounds = new Rect();
+ private int mTaskId;
+ private long mTime;
+ private boolean mTop;
+
+ @Rule
+ public final TestRule mRule = (base, description) -> new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ mMethodName = description.getMethodName();
+ mTasks.clear();
+ for (int i = 0; i < TASK_COUNT; i++) {
+ final Task t = new Task();
+ mTasks.add(t);
+ }
+ base.evaluate();
+
+ getInstrumentation().sendStatus(Activity.RESULT_OK, mTestResults);
+ }
+ };
+
+ @Test
+ public void test1ParamConsumer() {
+ evaluate(LAMBDA_FORM_REGULAR, () -> forAllTask(t -> t.doSomething(mTask)));
+ evaluate(LAMBDA_FORM_POOLED, () -> {
+ final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,
+ PooledLambda.__(Task.class), mTask);
+ forAllTask(c);
+ c.recycle();
+ });
+ }
+
+ @Test
+ public void test2PrimitiveParamsConsumer() {
+ // Not in Integer#IntegerCache (-128~127) for autoboxing, that may create new object.
+ mTaskId = 12345;
+ mTime = 54321;
+
+ evaluate(LAMBDA_FORM_REGULAR, () -> forAllTask(t -> t.doSomething(mTaskId, mTime)));
+ evaluate(LAMBDA_FORM_POOLED, () -> {
+ final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,
+ PooledLambda.__(Task.class), mTaskId, mTime);
+ forAllTask(c);
+ c.recycle();
+ });
+ }
+
+ @Test
+ public void test3ParamsPredicate() {
+ mTop = true;
+ // In Integer#IntegerCache.
+ mTaskId = 10;
+
+ evaluate(LAMBDA_FORM_REGULAR, () -> handleTask(t -> t.doSomething(mBounds, mTop, mTaskId)));
+ evaluate(LAMBDA_FORM_POOLED, () -> {
+ final PooledPredicate c = PooledLambda.obtainPredicate(Task::doSomething,
+ PooledLambda.__(Task.class), mBounds, mTop, mTaskId);
+ handleTask(c);
+ c.recycle();
+ });
+ }
+
+ @Test
+ public void testMessage() {
+ evaluate(LAMBDA_FORM_REGULAR, () -> {
+ final Message m = Message.obtain().setCallback(() -> mTask.doSomething(mTaskId, mTime));
+ m.getCallback().run();
+ m.recycle();
+ });
+ evaluate(LAMBDA_FORM_POOLED, () -> {
+ final Message m = PooledLambda.obtainMessage(Task::doSomething, mTask, mTaskId, mTime);
+ m.getCallback().run();
+ m.recycle();
+ });
+ }
+
+ @Test
+ public void testRunnable() {
+ evaluate(LAMBDA_FORM_REGULAR, () -> {
+ final Runnable r = mTask::doSomething;
+ r.run();
+ });
+ evaluate(LAMBDA_FORM_POOLED, () -> {
+ final Runnable r = PooledLambda.obtainRunnable(Task::doSomething, mTask).recycleOnUse();
+ r.run();
+ });
+ }
+
+ @Test
+ public void testMultiThread() {
+ final int numThread = 3;
+
+ final Runnable regularAction = () -> forAllTask(t -> t.doSomething(mTask));
+ final Runnable[] regularActions = new Runnable[numThread];
+ Arrays.fill(regularActions, regularAction);
+ evaluateMultiThread(LAMBDA_FORM_REGULAR, regularActions);
+
+ final Runnable pooledAction = () -> {
+ final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,
+ PooledLambda.__(Task.class), mTask);
+ forAllTask(c);
+ c.recycle();
+ };
+ final Runnable[] pooledActions = new Runnable[numThread];
+ Arrays.fill(pooledActions, pooledAction);
+ evaluateMultiThread(LAMBDA_FORM_POOLED, pooledActions);
+ }
+
+ private void forAllTask(Consumer<Task> callback) {
+ for (int i = mTasks.size() - 1; i >= 0; i--) {
+ callback.accept(mTasks.get(i));
+ }
+ }
+
+ private void handleTask(Predicate<Task> callback) {
+ for (int i = mTasks.size() - 1; i >= 0; i--) {
+ final Task task = mTasks.get(i);
+ if (callback.test(task)) {
+ return;
+ }
+ }
+ }
+
+ private void evaluate(String title, Runnable action) {
+ for (int i = 0; i < WARMUP_ITERATIONS; i++) {
+ action.run();
+ }
+ performGc();
+
+ final GcStatus startGcStatus = getGcStatus();
+ final long startTime = SystemClock.elapsedRealtime();
+ for (int i = 0; i < TEST_ITERATIONS; i++) {
+ action.run();
+ }
+ evaluateResult(title, startGcStatus, startTime);
+ }
+
+ private void evaluateMultiThread(String title, Runnable[] actions) {
+ performGc();
+
+ final CountDownLatch latch = new CountDownLatch(actions.length);
+ final GcStatus startGcStatus = getGcStatus();
+ final long startTime = SystemClock.elapsedRealtime();
+ for (Runnable action : actions) {
+ new Thread() {
+ @Override
+ public void run() {
+ for (int i = 0; i < TEST_ITERATIONS; i++) {
+ action.run();
+ }
+ latch.countDown();
+ };
+ }.start();
+ }
+ try {
+ latch.await();
+ } catch (InterruptedException ignored) {
+ }
+ evaluateResult(title, startGcStatus, startTime);
+ }
+
+ private void evaluateResult(String title, GcStatus startStatus, long startTime) {
+ final float elapsed = SystemClock.elapsedRealtime() - startTime;
+ // Sleep a while to see if GC may happen.
+ SystemClock.sleep(DELAY_AFTER_BENCH_MS);
+ final GcStatus endStatus = getGcStatus();
+ final GcInfo info = startStatus.calculateGcTime(endStatus, title, mTestResults);
+ mTestResults.putFloat("[" + title + "-execution-time]", elapsed);
+ Log.i(TAG, mMethodName + "_" + title + " execution time: "
+ + elapsed + "ms (avg=" + String.format("%.5f", elapsed / TEST_ITERATIONS) + "ms)"
+ + " GC time: " + String.format("%.3f", info.mTotalGcTime) + "ms"
+ + " GC paused time: " + String.format("%.3f", info.mTotalGcPausedTime) + "ms");
+ }
+
+ /** Cleans the test environment. */
+ private static void performGc() {
+ System.gc();
+ System.runFinalization();
+ System.gc();
+ }
+
+ private static GcStatus getGcStatus() {
+ if (DEBUG) {
+ Log.i(TAG, "===== Read GC dump =====");
+ }
+ final GcStatus status = new GcStatus();
+ final List<String> vmDump = getVmDump();
+ Assume.assumeFalse("VM dump is empty", vmDump.isEmpty());
+ for (String line : vmDump) {
+ status.visit(line);
+ if (line.startsWith("DALVIK THREADS")) {
+ break;
+ }
+ }
+ return status;
+ }
+
+ private static List<String> getVmDump() {
+ final int myPid = Process.myPid();
+ // Another approach Debug#dumpJavaBacktraceToFileTimeout requires setenforce 0.
+ Process.sendSignal(myPid, Process.SIGNAL_QUIT);
+ // Give a chance to handle the signal.
+ SystemClock.sleep(100);
+
+ String dump = null;
+ final String pattern = myPid + " written to: ";
+ final List<String> logs = shell("logcat -v brief -d tombstoned:I *:S");
+ for (int i = logs.size() - 1; i >= 0; i--) {
+ final String log = logs.get(i);
+ // Log pattern: Traces for pid 9717 written to: /data/anr/trace_07
+ final int pos = log.indexOf(pattern);
+ if (pos > 0) {
+ dump = log.substring(pattern.length() + pos);
+ if (!dump.startsWith("/data/anr/")) {
+ dump = "/data/anr/" + dump;
+ }
+ break;
+ }
+ }
+
+ Assume.assumeNotNull("Unable to find VM dump", dump);
+ // It requires system or root uid to read the trace.
+ return shell("cat " + dump);
+ }
+
+ private static List<String> shell(String command) {
+ final ParcelFileDescriptor.AutoCloseInputStream stream =
+ new ParcelFileDescriptor.AutoCloseInputStream(
+ getInstrumentation().getUiAutomation().executeShellCommand(command));
+ final ArrayList<String> lines = new ArrayList<>();
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(stream))) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ lines.add(line);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return lines;
+ }
+
+ /** An empty class which provides some methods with different type arguments. */
+ static class Task {
+ void doSomething() {
+ }
+
+ void doSomething(Task t) {
+ }
+
+ void doSomething(int taskId, long time) {
+ }
+
+ boolean doSomething(Rect bounds, boolean top, int taskId) {
+ return false;
+ }
+ }
+
+ static class ValPattern {
+ static final int TYPE_COUNT = 0;
+ static final int TYPE_TIME = 1;
+ static final String PATTERN_COUNT = "(\\d+)";
+ static final String PATTERN_TIME = "(\\d+\\.?\\d+)(\\w+)";
+ final String mRawPattern;
+ final Pattern mPattern;
+ final int mType;
+
+ int mIntValue;
+ float mFloatValue;
+
+ ValPattern(String p, int type) {
+ mRawPattern = p;
+ mPattern = Pattern.compile(
+ p + (type == TYPE_TIME ? PATTERN_TIME : PATTERN_COUNT) + ".*");
+ mType = type;
+ }
+
+ boolean visit(String line) {
+ final Matcher matcher = mPattern.matcher(line);
+ if (!matcher.matches()) {
+ return false;
+ }
+ final String value = matcher.group(1);
+ if (value == null) {
+ return false;
+ }
+ if (mType == TYPE_COUNT) {
+ mIntValue = Integer.parseInt(value);
+ return true;
+ }
+ final float time = Float.parseFloat(value);
+ final String unit = matcher.group(2);
+ if (unit == null) {
+ return false;
+ }
+ // Refer to art/libartbase/base/time_utils.cc
+ switch (unit) {
+ case "s":
+ mFloatValue = time * 1000;
+ break;
+ case "ms":
+ mFloatValue = time;
+ break;
+ case "us":
+ mFloatValue = time / 1000;
+ break;
+ case "ns":
+ mFloatValue = time / 1000 / 1000;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return mRawPattern + (mType == TYPE_TIME ? (mFloatValue + "ms") : mIntValue);
+ }
+ }
+
+ /** Parses the dump pattern of Heap::DumpGcPerformanceInfo. */
+ private static class GcStatus {
+ private static final int TOTAL_GC_TIME_INDEX = 1;
+ private static final int TOTAL_GC_PAUSED_TIME_INDEX = 5;
+
+ // Refer to art/runtime/gc/heap.cc
+ final ValPattern[] mPatterns = {
+ new ValPattern("Total GC count: ", ValPattern.TYPE_COUNT),
+ new ValPattern("Total GC time: ", ValPattern.TYPE_TIME),
+ new ValPattern("Total time waiting for GC to complete: ", ValPattern.TYPE_TIME),
+ new ValPattern("Total blocking GC count: ", ValPattern.TYPE_COUNT),
+ new ValPattern("Total blocking GC time: ", ValPattern.TYPE_TIME),
+ new ValPattern("Total mutator paused time: ", ValPattern.TYPE_TIME),
+ new ValPattern("Total number of allocations ", ValPattern.TYPE_COUNT),
+ new ValPattern("concurrent copying paused: Sum: ", ValPattern.TYPE_TIME),
+ new ValPattern("concurrent copying total time: ", ValPattern.TYPE_TIME),
+ new ValPattern("concurrent copying freed: ", ValPattern.TYPE_COUNT),
+ new ValPattern("Peak regions allocated ", ValPattern.TYPE_COUNT),
+ };
+
+ void visit(String dumpLine) {
+ for (ValPattern p : mPatterns) {
+ if (p.visit(dumpLine)) {
+ if (DEBUG) {
+ Log.i(TAG, " " + p);
+ }
+ }
+ }
+ }
+
+ GcInfo calculateGcTime(GcStatus newStatus, String title, Bundle result) {
+ Log.i(TAG, "===== GC status of " + title + " =====");
+ final GcInfo info = new GcInfo();
+ for (int i = 0; i < mPatterns.length; i++) {
+ final ValPattern p = mPatterns[i];
+ if (p.mType == ValPattern.TYPE_COUNT) {
+ final int diff = newStatus.mPatterns[i].mIntValue - p.mIntValue;
+ Log.i(TAG, " " + p.mRawPattern + diff);
+ if (diff > 0) {
+ result.putInt("[" + title + "] " + p.mRawPattern, diff);
+ }
+ continue;
+ }
+ final float diff = newStatus.mPatterns[i].mFloatValue - p.mFloatValue;
+ Log.i(TAG, " " + p.mRawPattern + diff + "ms");
+ if (diff > 0) {
+ result.putFloat("[" + title + "] " + p.mRawPattern + "(ms)", diff);
+ }
+ if (i == TOTAL_GC_TIME_INDEX) {
+ info.mTotalGcTime = diff;
+ } else if (i == TOTAL_GC_PAUSED_TIME_INDEX) {
+ info.mTotalGcPausedTime = diff;
+ }
+ }
+ return info;
+ }
+ }
+
+ private static class GcInfo {
+ float mTotalGcTime;
+ float mTotalGcPausedTime;
+ }
+}
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 7efe3c3..7ddbe95 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -130,7 +130,7 @@
"optimize/MultiApkGenerator.cpp",
"optimize/ResourceDeduper.cpp",
"optimize/ResourceFilter.cpp",
- "optimize/ResourcePathShortener.cpp",
+ "optimize/Obfuscator.cpp",
"optimize/VersionCollapser.cpp",
"process/SymbolTable.cpp",
"split/TableSplitter.cpp",
@@ -161,6 +161,7 @@
"ApkInfo.proto",
"Configuration.proto",
"Resources.proto",
+ "ResourceMetadata.proto",
"ResourcesInternal.proto",
"ValueTransformer.cpp",
],
@@ -218,6 +219,7 @@
srcs: [
"Configuration.proto",
"ResourcesInternal.proto",
+ "ResourceMetadata.proto",
"Resources.proto",
],
out: ["aapt2-protos.zip"],
diff --git a/tools/aapt2/ResourceMetadata.proto b/tools/aapt2/ResourceMetadata.proto
new file mode 100644
index 0000000..8eca54c
--- /dev/null
+++ b/tools/aapt2/ResourceMetadata.proto
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+syntax = "proto3";
+
+package aapt.pb;
+
+option java_package = "com.android.aapt";
+option java_multiple_files = true;
+
+message ResourceMappings {
+ ShortenedPathsMap shortened_paths = 1;
+ CollapsedNamesMap collapsed_names = 2;
+}
+
+// Metadata relating to "aapt2 optimize --shorten-resource-paths"
+message ShortenedPathsMap {
+ // Maps shorted paths (e.g. "res/foo.xml") to their original names (e.g.
+ // "res/xml/file_with_long_name.xml").
+ message ResourcePathMapping {
+ string shortened_path = 1;
+ string original_path = 2;
+ }
+ repeated ResourcePathMapping resource_paths = 1;
+}
+
+// Metadata relating to "aapt2 optimize --collapse-resource-names"
+message CollapsedNamesMap {
+ // Maps resource IDs (e.g. 0x7f123456) to their original names (e.g.
+ // "package:type/entry").
+ message ResourceNameMapping {
+ uint32 id = 1;
+ string name = 2;
+ }
+ repeated ResourceNameMapping resource_names = 1;
+}
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index e37c2d4..9feaf52 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -16,7 +16,11 @@
#include "Optimize.h"
+#include <map>
#include <memory>
+#include <set>
+#include <string>
+#include <utility>
#include <vector>
#include "Diagnostics.h"
@@ -38,9 +42,9 @@
#include "io/BigBufferStream.h"
#include "io/Util.h"
#include "optimize/MultiApkGenerator.h"
+#include "optimize/Obfuscator.h"
#include "optimize/ResourceDeduper.h"
#include "optimize/ResourceFilter.h"
-#include "optimize/ResourcePathShortener.h"
#include "optimize/VersionCollapser.h"
#include "split/TableSplitter.h"
#include "util/Files.h"
@@ -114,11 +118,11 @@
}
private:
- DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
-
StdErrDiagnostics diagnostics_;
bool verbose_ = false;
int sdk_version_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
};
class Optimizer {
@@ -151,8 +155,8 @@
}
if (options_.shorten_resource_paths) {
- ResourcePathShortener shortener(options_.table_flattener_options.shortened_path_map);
- if (!shortener.Consume(context_, apk->GetResourceTable())) {
+ Obfuscator obfuscator(options_.table_flattener_options.shortened_path_map);
+ if (!obfuscator.Consume(context_, apk->GetResourceTable())) {
context_->GetDiagnostics()->Error(android::DiagMessage()
<< "failed shortening resource paths");
return 1;
diff --git a/tools/aapt2/optimize/ResourcePathShortener.cpp b/tools/aapt2/optimize/Obfuscator.cpp
similarity index 83%
rename from tools/aapt2/optimize/ResourcePathShortener.cpp
rename to tools/aapt2/optimize/Obfuscator.cpp
index 7ff9bf5..f704f26 100644
--- a/tools/aapt2/optimize/ResourcePathShortener.cpp
+++ b/tools/aapt2/optimize/Obfuscator.cpp
@@ -14,28 +14,25 @@
* limitations under the License.
*/
-#include "optimize/ResourcePathShortener.h"
+#include "optimize/Obfuscator.h"
#include <set>
+#include <string>
#include <unordered_set>
-#include "androidfw/StringPiece.h"
-
#include "ResourceTable.h"
#include "ValueVisitor.h"
+#include "androidfw/StringPiece.h"
#include "util/Util.h"
-
-static const std::string base64_chars =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "abcdefghijklmnopqrstuvwxyz"
- "0123456789-_";
+static const char base64_chars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789-_";
namespace aapt {
-ResourcePathShortener::ResourcePathShortener(
- std::map<std::string, std::string>& path_map_out)
- : path_map_(path_map_out) {
+Obfuscator::Obfuscator(std::map<std::string, std::string>& path_map_out) : path_map_(path_map_out) {
}
std::string ShortenFileName(const android::StringPiece& file_path, int output_length) {
@@ -50,7 +47,6 @@
return result;
}
-
// Return the optimal hash length such that at most 10% of resources collide in
// their shortened path.
// Reference: http://matt.might.net/articles/counting-hash-collisions/
@@ -63,7 +59,7 @@
}
std::string GetShortenedPath(const android::StringPiece& shortened_filename,
- const android::StringPiece& extension, int collision_count) {
+ const android::StringPiece& extension, int collision_count) {
std::string shortened_path = "res/" + shortened_filename.to_string();
if (collision_count > 0) {
shortened_path += std::to_string(collision_count);
@@ -76,12 +72,12 @@
// underlying filepath as key rather than the integer address. This is to ensure
// determinism of output for colliding files.
struct PathComparator {
- bool operator() (const FileReference* lhs, const FileReference* rhs) const {
- return lhs->path->compare(*rhs->path);
- }
+ bool operator()(const FileReference* lhs, const FileReference* rhs) const {
+ return lhs->path->compare(*rhs->path);
+ }
};
-bool ResourcePathShortener::Consume(IAaptContext* context, ResourceTable* table) {
+bool Obfuscator::Consume(IAaptContext* context, ResourceTable* table) {
// used to detect collisions
std::unordered_set<std::string> shortened_paths;
std::set<FileReference*, PathComparator> file_refs;
@@ -103,8 +99,7 @@
util::ExtractResFilePathParts(*file_ref->path, &res_subdir, &actual_filename, &extension);
// Android detects ColorStateLists via pathname, skip res/color*
- if (util::StartsWith(res_subdir, "res/color"))
- continue;
+ if (util::StartsWith(res_subdir, "res/color")) continue;
std::string shortened_filename = ShortenFileName(*file_ref->path, num_chars);
int collision_count = 0;
diff --git a/tools/aapt2/optimize/ResourcePathShortener.h b/tools/aapt2/optimize/Obfuscator.h
similarity index 72%
rename from tools/aapt2/optimize/ResourcePathShortener.h
rename to tools/aapt2/optimize/Obfuscator.h
index f1074ef..1ea32db 100644
--- a/tools/aapt2/optimize/ResourcePathShortener.h
+++ b/tools/aapt2/optimize/Obfuscator.h
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-#ifndef AAPT_OPTIMIZE_RESOURCEPATHSHORTENER_H
-#define AAPT_OPTIMIZE_RESOURCEPATHSHORTENER_H
+#ifndef TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
+#define TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
#include <map>
+#include <string>
#include "android-base/macros.h"
-
#include "process/IResourceTableConsumer.h"
namespace aapt {
@@ -28,17 +28,17 @@
class ResourceTable;
// Maps resources in the apk to shortened paths.
-class ResourcePathShortener : public IResourceTableConsumer {
+class Obfuscator : public IResourceTableConsumer {
public:
- explicit ResourcePathShortener(std::map<std::string, std::string>& path_map_out);
+ explicit Obfuscator(std::map<std::string, std::string>& path_map_out);
bool Consume(IAaptContext* context, ResourceTable* table) override;
private:
- DISALLOW_COPY_AND_ASSIGN(ResourcePathShortener);
std::map<std::string, std::string>& path_map_;
+ DISALLOW_COPY_AND_ASSIGN(Obfuscator);
};
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_OPTIMIZE_RESOURCEPATHSHORTENER_H
+#endif // TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
diff --git a/tools/aapt2/optimize/ResourcePathShortener_test.cpp b/tools/aapt2/optimize/Obfuscator_test.cpp
similarity index 80%
rename from tools/aapt2/optimize/ResourcePathShortener_test.cpp
rename to tools/aapt2/optimize/Obfuscator_test.cpp
index f5a02be..a3339d4 100644
--- a/tools/aapt2/optimize/ResourcePathShortener_test.cpp
+++ b/tools/aapt2/optimize/Obfuscator_test.cpp
@@ -14,15 +14,18 @@
* limitations under the License.
*/
-#include "optimize/ResourcePathShortener.h"
+#include "optimize/Obfuscator.h"
+
+#include <memory>
+#include <string>
#include "ResourceTable.h"
#include "test/Test.h"
using ::aapt::test::GetValue;
+using ::testing::Eq;
using ::testing::Not;
using ::testing::NotNull;
-using ::testing::Eq;
android::StringPiece GetExtension(android::StringPiece path) {
auto iter = std::find(path.begin(), path.end(), '.');
@@ -30,16 +33,15 @@
}
void FillTable(aapt::test::ResourceTableBuilder& builder, int start, int end) {
- for (int i=start; i<end; i++) {
- builder.AddFileReference(
- "android:drawable/xmlfile" + std::to_string(i),
- "res/drawable/xmlfile" + std::to_string(i) + ".xml");
+ for (int i = start; i < end; i++) {
+ builder.AddFileReference("android:drawable/xmlfile" + std::to_string(i),
+ "res/drawable/xmlfile" + std::to_string(i) + ".xml");
}
}
namespace aapt {
-TEST(ResourcePathShortenerTest, FileRefPathsChangedInResourceTable) {
+TEST(ObfuscatorTest, FileRefPathsChangedInResourceTable) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
std::unique_ptr<ResourceTable> table =
@@ -50,7 +52,7 @@
.Build();
std::map<std::string, std::string> path_map;
- ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get()));
+ ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
// Expect that the path map is populated
ASSERT_THAT(path_map.find("res/drawables/xmlfile.xml"), Not(Eq(path_map.end())));
@@ -64,39 +66,36 @@
EXPECT_THAT(path_map["res/drawables/xmlfile.xml"],
Not(Eq(path_map["res/drawables/xmlfile2.xml"])));
- FileReference* ref =
- GetValue<FileReference>(table.get(), "android:drawable/xmlfile");
+ FileReference* ref = GetValue<FileReference>(table.get(), "android:drawable/xmlfile");
ASSERT_THAT(ref, NotNull());
// The map correctly points to the new location of the file
EXPECT_THAT(path_map["res/drawables/xmlfile.xml"], Eq(*ref->path));
// Strings should not be affected, only file paths
- EXPECT_THAT(
- *GetValue<String>(table.get(), "android:string/string")->value,
+ EXPECT_THAT(*GetValue<String>(table.get(), "android:string/string")->value,
Eq("res/should/still/be/the/same.png"));
EXPECT_THAT(path_map.find("res/should/still/be/the/same.png"), Eq(path_map.end()));
}
-TEST(ResourcePathShortenerTest, SkipColorFileRefPaths) {
+TEST(ObfuscatorTest, SkipColorFileRefPaths) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
.AddFileReference("android:color/colorlist", "res/color/colorlist.xml")
- .AddFileReference("android:color/colorlist",
- "res/color-mdp-v21/colorlist.xml",
+ .AddFileReference("android:color/colorlist", "res/color-mdp-v21/colorlist.xml",
test::ParseConfigOrDie("mdp-v21"))
.Build();
std::map<std::string, std::string> path_map;
- ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get()));
+ ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
// Expect that the path map to not contain the ColorStateList
ASSERT_THAT(path_map.find("res/color/colorlist.xml"), Eq(path_map.end()));
ASSERT_THAT(path_map.find("res/color-mdp-v21/colorlist.xml"), Eq(path_map.end()));
}
-TEST(ResourcePathShortenerTest, KeepExtensions) {
+TEST(ObfuscatorTest, KeepExtensions) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
std::string original_xml_path = "res/drawable/xmlfile.xml";
@@ -109,7 +108,7 @@
.Build();
std::map<std::string, std::string> path_map;
- ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get()));
+ ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
// Expect that the path map is populated
ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end())));
@@ -122,7 +121,7 @@
EXPECT_THAT(GetExtension(path_map[original_png_path]), Eq(android::StringPiece(".png")));
}
-TEST(ResourcePathShortenerTest, DeterministicallyHandleCollisions) {
+TEST(ObfuscatorTest, DeterministicallyHandleCollisions) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
// 4000 resources is the limit at which the hash space is expanded to 3
@@ -135,27 +134,27 @@
FillTable(builder1, 0, kNumResources);
std::unique_ptr<ResourceTable> table1 = builder1.Build();
std::map<std::string, std::string> expected_mapping;
- ASSERT_TRUE(ResourcePathShortener(expected_mapping).Consume(context.get(), table1.get()));
+ ASSERT_TRUE(Obfuscator(expected_mapping).Consume(context.get(), table1.get()));
// We are trying to ensure lack of non-determinism, it is not simple to prove
// a negative, thus we must try the test a few times so that the test itself
// is non-flaky. Basically create the pathmap 5 times from the same set of
// resources but a different order of addition and then ensure they are always
// mapped to the same short path.
- for (int i=0; i<kNumTries; i++) {
+ for (int i = 0; i < kNumTries; i++) {
test::ResourceTableBuilder builder2;
// This loop adds resources to the resource table in the range of
// [0:kNumResources). Adding the file references in different order makes
// non-determinism more likely to surface. Thus we add resources
// [start_index:kNumResources) first then [0:start_index). We also use a
// different start_index each run.
- int start_index = (kNumResources/kNumTries)*i;
+ int start_index = (kNumResources / kNumTries) * i;
FillTable(builder2, start_index, kNumResources);
FillTable(builder2, 0, start_index);
std::unique_ptr<ResourceTable> table2 = builder2.Build();
std::map<std::string, std::string> actual_mapping;
- ASSERT_TRUE(ResourcePathShortener(actual_mapping).Consume(context.get(), table2.get()));
+ ASSERT_TRUE(Obfuscator(actual_mapping).Consume(context.get(), table2.get()));
for (auto& item : actual_mapping) {
ASSERT_THAT(expected_mapping[item.first], Eq(item.second));
@@ -163,4 +162,4 @@
}
}
-} // namespace aapt
+} // namespace aapt