Merge "Increase app updater balances."
diff --git a/core/api/current.txt b/core/api/current.txt
index 3331603..9a76523 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4426,6 +4426,7 @@
method public void readFromParcel(android.os.Parcel);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.ActivityManager.MemoryInfo> CREATOR;
+ field public long advertisedMem;
field public long availMem;
field public boolean lowMemory;
field public long threshold;
@@ -11941,6 +11942,7 @@
field @Deprecated public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
field public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
field public static final String FEATURE_CONTROLS = "android.software.controls";
+ field public static final String FEATURE_CREDENTIALS = "android.software.credentials";
field public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
field public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded";
field public static final String FEATURE_ETHERNET = "android.hardware.ethernet";
@@ -45194,6 +45196,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();
@@ -51880,10 +51883,11 @@
method public void setSpeechStateChangeTypes(int);
method public void writeToParcel(android.os.Parcel, int);
field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4
+ field public static final int CONTENT_CHANGE_TYPE_CONTENT_INVALID = 1024; // 0x400
field public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 512; // 0x200
field public static final int CONTENT_CHANGE_TYPE_DRAG_DROPPED = 256; // 0x100
field public static final int CONTENT_CHANGE_TYPE_DRAG_STARTED = 128; // 0x80
- field public static final int CONTENT_CHANGE_TYPE_INVALID = 1024; // 0x400
+ field public static final int CONTENT_CHANGE_TYPE_ERROR = 2048; // 0x800
field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10
field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20
field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8
@@ -53316,6 +53320,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 8ae16df6..da2a76a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -29,6 +29,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -2812,6 +2813,15 @@
*/
public static class MemoryInfo implements Parcelable {
/**
+ * The advertised memory of the system, as the end user would encounter in a retail display
+ * environment. This value might be different from {@code totalMem}. This could be due to
+ * many reasons. For example, the ODM could reserve part of the memory for the Trusted
+ * Execution Environment (TEE) which the kernel doesn't have access or knowledge about it.
+ */
+ @SuppressLint("MutableBareField")
+ public long advertisedMem;
+
+ /**
* The available memory on the system. This number should not
* be considered absolute: due to the nature of the kernel, a significant
* portion of this memory is actually in use and needed for the overall
@@ -2860,6 +2870,7 @@
}
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(advertisedMem);
dest.writeLong(availMem);
dest.writeLong(totalMem);
dest.writeLong(threshold);
@@ -2871,6 +2882,7 @@
}
public void readFromParcel(Parcel source) {
+ advertisedMem = source.readLong();
availMem = source.readLong();
totalMem = source.readLong();
threshold = source.readLong();
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/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/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8e2a5ea..db991dc 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4187,6 +4187,13 @@
public static final String FEATURE_WINDOW_MAGNIFICATION =
"android.software.window_magnification";
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+ * supports retrieval of user credentials, via integration with credential providers.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CREDENTIALS = "android.software.credentials";
+
/** @hide */
public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
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/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java
index 6c42776..15eae09 100644
--- a/core/java/android/hardware/DataSpace.java
+++ b/core/java/android/hardware/DataSpace.java
@@ -385,14 +385,6 @@
*/
public static final int RANGE_EXTENDED = 3 << 27;
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true, value = {
- DATASPACE_DEPTH,
- DATASPACE_DYNAMIC_DEPTH,
- })
- public @interface DataSpaceDepth {};
-
/**
* Depth.
*
@@ -407,13 +399,6 @@
*/
public static final int DATASPACE_DYNAMIC_DEPTH = 4098;
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true, value = {
- DATASPACE_HEIF,
- })
- public @interface DataSpaceFileFormat {};
-
/**
* High Efficiency Image File Format (HEIF).
*
@@ -442,7 +427,7 @@
DATASPACE_DCI_P3,
DATASPACE_SRGB_LINEAR
})
- public @interface NamedDataSpace {};
+ public @interface ColorDataSpace {};
/**
* Default-assumption data space, when not explicitly specified.
@@ -635,6 +620,30 @@
*/
public static final int DATASPACE_SRGB_LINEAR = 138477568;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {
+ DATASPACE_DEPTH,
+ DATASPACE_DYNAMIC_DEPTH,
+ DATASPACE_HEIF,
+ DATASPACE_UNKNOWN,
+ DATASPACE_SCRGB_LINEAR,
+ DATASPACE_SRGB,
+ DATASPACE_SCRGB,
+ DATASPACE_DISPLAY_P3,
+ DATASPACE_BT2020_HLG,
+ DATASPACE_BT2020_PQ,
+ DATASPACE_ADOBE_RGB,
+ DATASPACE_JFIF,
+ DATASPACE_BT601_625,
+ DATASPACE_BT601_525,
+ DATASPACE_BT2020,
+ DATASPACE_BT709,
+ DATASPACE_DCI_P3,
+ DATASPACE_SRGB_LINEAR
+ })
+ public @interface NamedDataSpace {};
+
private DataSpace() {}
/**
@@ -647,7 +656,7 @@
*
* @return The int dataspace packed by standard, transfer and range value
*/
- public static @NamedDataSpace int pack(@DataSpaceStandard int standard,
+ public static @ColorDataSpace int pack(@DataSpaceStandard int standard,
@DataSpaceTransfer int transfer,
@DataSpaceRange int range) {
if ((standard & STANDARD_MASK) != standard) {
@@ -669,7 +678,7 @@
*
* @return The standard aspect
*/
- public static @DataSpaceStandard int getStandard(@NamedDataSpace int dataSpace) {
+ public static @DataSpaceStandard int getStandard(@ColorDataSpace int dataSpace) {
@DataSpaceStandard int standard = dataSpace & STANDARD_MASK;
return standard;
}
@@ -681,7 +690,7 @@
*
* @return The transfer aspect
*/
- public static @DataSpaceTransfer int getTransfer(@NamedDataSpace int dataSpace) {
+ public static @DataSpaceTransfer int getTransfer(@ColorDataSpace int dataSpace) {
@DataSpaceTransfer int transfer = dataSpace & TRANSFER_MASK;
return transfer;
}
@@ -693,7 +702,7 @@
*
* @return The range aspect
*/
- public static @DataSpaceRange int getRange(@NamedDataSpace int dataSpace) {
+ public static @DataSpaceRange int getRange(@ColorDataSpace int dataSpace) {
@DataSpaceRange int range = dataSpace & RANGE_MASK;
return range;
}
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/FileUtils.java b/core/java/android/os/FileUtils.java
index edfcb3d..d5c3de1 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -54,6 +54,7 @@
import android.system.Os;
import android.system.StructStat;
import android.text.TextUtils;
+import android.util.DataUnit;
import android.util.Log;
import android.util.Slog;
import android.webkit.MimeTypeMap;
@@ -83,6 +84,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -1309,6 +1311,85 @@
return val * pow;
}
+ private static long toBytes(long value, String unit) {
+ unit = unit.toUpperCase();
+
+ if (List.of("B").contains(unit)) {
+ return value;
+ }
+
+ if (List.of("K", "KB").contains(unit)) {
+ return DataUnit.KILOBYTES.toBytes(value);
+ }
+
+ if (List.of("M", "MB").contains(unit)) {
+ return DataUnit.MEGABYTES.toBytes(value);
+ }
+
+ if (List.of("G", "GB").contains(unit)) {
+ return DataUnit.GIGABYTES.toBytes(value);
+ }
+
+ if (List.of("KI", "KIB").contains(unit)) {
+ return DataUnit.KIBIBYTES.toBytes(value);
+ }
+
+ if (List.of("MI", "MIB").contains(unit)) {
+ return DataUnit.MEBIBYTES.toBytes(value);
+ }
+
+ if (List.of("GI", "GIB").contains(unit)) {
+ return DataUnit.GIBIBYTES.toBytes(value);
+ }
+
+ return Long.MIN_VALUE;
+ }
+
+ /**
+ * @param fmtSize The string that contains the size to be parsed. The
+ * expected format is:
+ *
+ * <p>"^((\\s*[-+]?[0-9]+)\\s*(B|K|KB|M|MB|G|GB|Ki|KiB|Mi|MiB|Gi|GiB)\\s*)$"
+ *
+ * <p>For example: 10Kb, 500GiB, 100mb. The unit is not case sensitive.
+ *
+ * @return the size in bytes. If {@code fmtSize} has invalid format, it
+ * returns {@link Long#MIN_VALUE}.
+ * @hide
+ */
+ public static long parseSize(@Nullable String fmtSize) {
+ if (fmtSize == null || fmtSize.isBlank()) {
+ return Long.MIN_VALUE;
+ }
+
+ int sign = 1;
+ fmtSize = fmtSize.trim();
+ char first = fmtSize.charAt(0);
+ if (first == '-' || first == '+') {
+ if (first == '-') {
+ sign = -1;
+ }
+
+ fmtSize = fmtSize.replace(first + "", "");
+ }
+
+ int index = 0;
+ // Find the last index of the value in fmtSize.
+ while (index < fmtSize.length() && Character.isDigit(fmtSize.charAt(index))) {
+ index++;
+ }
+
+ // Check if number and units are present.
+ if (index == 0 || index == fmtSize.length()) {
+ return Long.MIN_VALUE;
+ }
+
+ long value = sign * Long.valueOf(fmtSize.substring(0, index));
+ String unit = fmtSize.substring(index).trim();
+
+ return toBytes(value, unit);
+ }
+
/**
* Closes the given object quietly, ignoring any checked exceptions. Does
* nothing if the given object is {@code null}.
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 14082f3..c943a3d 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -26,6 +26,7 @@
import android.annotation.UptimeMillisLong;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build.VERSION_CODES;
+import android.sysprop.MemoryProperties;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -1330,6 +1331,24 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public static final native void sendSignalQuiet(int pid, int signal);
+ /**
+ * @return The advertised memory of the system, as the end user would encounter in a retail
+ * display environment. If the advertised memory is not defined, it returns
+ * {@code getTotalMemory()} rounded.
+ *
+ * @hide
+ */
+ public static final long getAdvertisedMem() {
+ String formatSize = MemoryProperties.memory_ddr_size().orElse("0KB");
+ long memSize = FileUtils.parseSize(formatSize);
+
+ if (memSize == Long.MIN_VALUE) {
+ return FileUtils.roundStorageSize(getTotalMemory());
+ }
+
+ return memSize;
+ }
+
/** @hide */
@UnsupportedAppUsage
public static final native long getFreeMemory();
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/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/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index f2c8355..f86f51fc 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -24,7 +24,6 @@
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
-import android.widget.TextView;
import com.android.internal.util.BitUtils;
@@ -688,15 +687,27 @@
/**
* Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
- * It means the content is invalid or associated with an error.
- * For example, text that sets an error message, such as when input isn't in a valid format,
- * should send this event and use {@link AccessibilityNodeInfo#setError} to
- * provide more context.
+ * The source node changed its content validity returned by
+ * {@link AccessibilityNodeInfo#isContentInvalid}.
+ * The view changing content validity should call
+ * {@link AccessibilityNodeInfo#setContentInvalid} and then send this event.
*
- * @see AccessibilityNodeInfo#setError
- * @see TextView#setError
+ * @see AccessibilityNodeInfo#isContentInvalid
+ * @see AccessibilityNodeInfo#setContentInvalid
*/
- public static final int CONTENT_CHANGE_TYPE_INVALID = 0x0000400;
+ public static final int CONTENT_CHANGE_TYPE_CONTENT_INVALID = 0x0000400;
+
+ /**
+ * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+ * The source node changed its erroneous content's error message returned by
+ * {@link AccessibilityNodeInfo#getError}.
+ * The view changing erroneous content's error message should call
+ * {@link AccessibilityNodeInfo#setError} and then send this event.
+ *
+ * @see AccessibilityNodeInfo#getError
+ * @see AccessibilityNodeInfo#setError
+ */
+ public static final int CONTENT_CHANGE_TYPE_ERROR = 0x0000800;
/** Change type for {@link #TYPE_SPEECH_STATE_CHANGE} event: another service is speaking. */
public static final int SPEECH_STATE_SPEAKING_START = 0x00000001;
@@ -823,7 +834,8 @@
CONTENT_CHANGE_TYPE_DRAG_STARTED,
CONTENT_CHANGE_TYPE_DRAG_DROPPED,
CONTENT_CHANGE_TYPE_DRAG_CANCELLED,
- CONTENT_CHANGE_TYPE_INVALID,
+ CONTENT_CHANGE_TYPE_CONTENT_INVALID,
+ CONTENT_CHANGE_TYPE_ERROR,
})
public @interface ContentChangeTypes {}
@@ -1090,7 +1102,9 @@
case CONTENT_CHANGE_TYPE_DRAG_STARTED: return "CONTENT_CHANGE_TYPE_DRAG_STARTED";
case CONTENT_CHANGE_TYPE_DRAG_DROPPED: return "CONTENT_CHANGE_TYPE_DRAG_DROPPED";
case CONTENT_CHANGE_TYPE_DRAG_CANCELLED: return "CONTENT_CHANGE_TYPE_DRAG_CANCELLED";
- case CONTENT_CHANGE_TYPE_INVALID: return "CONTENT_CHANGE_TYPE_INVALID";
+ case CONTENT_CHANGE_TYPE_CONTENT_INVALID:
+ return "CONTENT_CHANGE_TYPE_CONTENT_INVALID";
+ case CONTENT_CHANGE_TYPE_ERROR: return "CONTENT_CHANGE_TYPE_ERROR";
default: return Integer.toHexString(type);
}
}
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/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index 6bf2474..514df59 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -175,10 +175,7 @@
*/
public void onActivityDestroyed() {
synchronized (mLock) {
- if (DEBUG) {
- Log.i(TAG,
- "onActivityDestroyed(): mCurrentState is " + stateToString(mCurrentState));
- }
+ Log.i(TAG, "onActivityDestroyed(): mCurrentState is " + stateToString(mCurrentState));
if (mCurrentState != STATE_UI_TRANSLATION_FINISHED) {
notifyTranslationFinished(/* activityDestroyed= */ 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 38c8801..d11fa5f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7650,7 +7650,8 @@
createEditorIfNeeded();
mEditor.setError(error, icon);
notifyViewAccessibilityStateChangedIfNeeded(
- AccessibilityEvent.CONTENT_CHANGE_TYPE_INVALID);
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_ERROR
+ | AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_INVALID);
}
@Override
@@ -9390,7 +9391,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)) {
@@ -9418,7 +9419,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);
@@ -9472,7 +9473,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)) {
@@ -12221,6 +12222,11 @@
}
}
+ @Override
+ public boolean isAutoHandwritingEnabled() {
+ return super.isAutoHandwritingEnabled() && !isAnyPasswordInputType();
+ }
+
/** @hide */
@Override
public boolean isStylusHandwritingAvailable() {
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/res/Android.bp b/core/res/Android.bp
index 93ce783..7e17840 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -130,6 +130,10 @@
// Allow overlay to add resource
"--auto-add-overlay",
+
+ // Framework resources benefit tremendously from enabling sparse encoding, saving tens
+ // of MBs in size and RAM use.
+ "--enable-sparse-encoding",
],
resource_zips: [
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index 32c3a26..91fbe00 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -532,6 +532,56 @@
}
@Test
+ public void testParseSize() {
+ assertEquals(0L, FileUtils.parseSize("0MB"));
+ assertEquals(1_024L, FileUtils.parseSize("1024b"));
+ assertEquals(-1L, FileUtils.parseSize(" -1 b "));
+ assertEquals(0L, FileUtils.parseSize(" -0 gib "));
+ assertEquals(1_000L, FileUtils.parseSize("1K"));
+ assertEquals(1_000L, FileUtils.parseSize("1KB"));
+ assertEquals(10_000L, FileUtils.parseSize("10KB"));
+ assertEquals(100_000L, FileUtils.parseSize("100KB"));
+ assertEquals(1_000_000L, FileUtils.parseSize("1000KB"));
+ assertEquals(1_024_000L, FileUtils.parseSize("1000KiB"));
+ assertEquals(70_000_000L, FileUtils.parseSize("070M"));
+ assertEquals(70_000_000L, FileUtils.parseSize("070MB"));
+ assertEquals(73_400_320L, FileUtils.parseSize("70MiB"));
+ assertEquals(700_000_000L, FileUtils.parseSize("700000KB"));
+ assertEquals(200_000_000L, FileUtils.parseSize("+200MB"));
+ assertEquals(1_000_000_000L, FileUtils.parseSize("1000MB"));
+ assertEquals(1_000_000_000L, FileUtils.parseSize("+1000 mb"));
+ assertEquals(644_245_094_400L, FileUtils.parseSize("600GiB"));
+ assertEquals(999_000_000_000L, FileUtils.parseSize("999GB"));
+ assertEquals(999_000_000_000L, FileUtils.parseSize("999 gB"));
+ assertEquals(9_999_000_000_000L, FileUtils.parseSize("9999GB"));
+ assertEquals(9_000_000_000_000L, FileUtils.parseSize(" 9000 GB "));
+ assertEquals(1_234_000_000_000L, FileUtils.parseSize(" 1234 GB "));
+ assertEquals(1_234_567_890_000L, FileUtils.parseSize(" 1234567890 KB "));
+ }
+
+ @Test
+ public void testParseSize_invalidArguments() {
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize(null));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("null"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize(""));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize(" "));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("KB"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("123 dd"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("Invalid"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize(" ABC890 KB "));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("-=+90 KB "));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("123"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("--123"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("-KB"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("++123"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("+"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("+ 1 +"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("+--+ 1 +"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("1GB+"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize(" + 1234567890 KB "));
+ }
+
+ @Test
public void testTranslateMode() throws Exception {
assertTranslate("r", O_RDONLY, MODE_READ_ONLY);
diff --git a/core/tests/coretests/src/android/os/ProcessTest.java b/core/tests/coretests/src/android/os/ProcessTest.java
index ae4edb9..52846df 100644
--- a/core/tests/coretests/src/android/os/ProcessTest.java
+++ b/core/tests/coretests/src/android/os/ProcessTest.java
@@ -72,4 +72,7 @@
assertEquals(-1, Process.getThreadGroupLeader(BAD_PID));
}
+ public void testGetAdvertisedMem() {
+ assertTrue(Process.getTotalMemory() <= Process.getAdvertisedMem());
+ }
}
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/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index ca3c847..31df474 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -24,7 +24,7 @@
import android.annotation.SuppressAutoDoc;
import android.annotation.SuppressLint;
import android.hardware.DataSpace;
-import android.hardware.DataSpace.NamedDataSpace;
+import android.hardware.DataSpace.ColorDataSpace;
import android.util.SparseIntArray;
import libcore.util.NativeAllocationRegistry;
@@ -1406,7 +1406,7 @@
*/
@SuppressLint("MethodNameUnits")
@Nullable
- public static ColorSpace getFromDataSpace(@NamedDataSpace int dataSpace) {
+ public static ColorSpace getFromDataSpace(@ColorDataSpace int dataSpace) {
int index = sDataToColorSpaces.get(dataSpace, -1);
if (index != -1) {
return ColorSpace.get(index);
@@ -1425,7 +1425,7 @@
* @return the dataspace value.
*/
@SuppressLint("MethodNameUnits")
- public @NamedDataSpace int getDataSpace() {
+ public @ColorDataSpace int getDataSpace() {
int index = sDataToColorSpaces.indexOfValue(getId());
if (index != -1) {
return sDataToColorSpaces.keyAt(index);
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/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 835d6e9..38a3124 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -105,7 +105,8 @@
)
}
- pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java)
+ pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java,
+ true /* allowMultiple */)
context.contentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
false,
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/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/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 27e9af9..412dc05 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -912,6 +912,12 @@
if (view.isDisplayConfigured()) {
view.unconfigureDisplay();
}
+
+ if (mCancelAodTimeoutAction != null) {
+ mCancelAodTimeoutAction.run();
+ mCancelAodTimeoutAction = null;
+ }
+ mIsAodInterruptActive = false;
}
/**
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/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 54c1b28..38d9d021 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -101,8 +101,8 @@
public static final UnreleasedFlag MODERN_BOUNCER = new UnreleasedFlag(208);
/** Whether UserSwitcherActivity should use modern architecture. */
- public static final UnreleasedFlag MODERN_USER_SWITCHER_ACTIVITY =
- new UnreleasedFlag(209, true);
+ public static final ReleasedFlag MODERN_USER_SWITCHER_ACTIVITY =
+ new ReleasedFlag(209, true);
/** Whether the new implementation of UserSwitcherController should be used. */
public static final UnreleasedFlag REFACTORED_USER_SWITCHER_CONTROLLER =
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/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/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/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index a6c0539..53e30fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -18,6 +18,7 @@
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
import static junit.framework.Assert.assertEquals;
@@ -687,6 +688,58 @@
}
@Test
+ public void aodInterruptCancelTimeoutActionWhenFingerUp() throws RemoteException {
+ when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+
+ // GIVEN AOD interrupt
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mScreenObserver.onScreenTurnedOn();
+ mFgExecutor.runAllReady();
+ mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
+ mFgExecutor.runAllReady();
+
+ // Configure UdfpsView to accept the ACTION_UP event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+
+ // WHEN ACTION_UP is received
+ verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+ MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
+ mBiometricsExecutor.runAllReady();
+ upEvent.recycle();
+
+ // Configure UdfpsView to accept the ACTION_DOWN event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+
+ // WHEN ACTION_DOWN is received
+ MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+ mBiometricsExecutor.runAllReady();
+ downEvent.recycle();
+
+ // WHEN ACTION_MOVE is received
+ MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+ mBiometricsExecutor.runAllReady();
+ moveEvent.recycle();
+ mFgExecutor.runAllReady();
+
+ // Configure UdfpsView to accept the finger up event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+
+ // WHEN it times out
+ mFgExecutor.advanceClockToNext();
+ mFgExecutor.runAllReady();
+
+ // THEN the display should be unconfigured once. If the timeout action is not
+ // cancelled, the display would be unconfigured twice which would cause two
+ // FP attempts.
+ verify(mUdfpsView, times(1)).unconfigureDisplay();
+ }
+
+ @Test
public void aodInterruptScreenOff() throws RemoteException {
// GIVEN screen off
mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 131eac6..8be138a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -113,7 +113,7 @@
registry.isEnabled = true
verify(mockPluginManager)
- .addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java))
+ .addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java), eq(true))
pluginListener = captor.value
}
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/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d98dffb..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;
@@ -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;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 992d416..d7b3848 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -27,6 +27,7 @@
import static android.os.Process.SYSTEM_UID;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
+import static android.os.Process.getAdvertisedMem;
import static android.os.Process.getFreeMemory;
import static android.os.Process.getTotalMemory;
import static android.os.Process.killProcessQuiet;
@@ -1532,6 +1533,7 @@
void getMemoryInfo(ActivityManager.MemoryInfo outInfo) {
final long homeAppMem = getMemLevel(HOME_APP_ADJ);
final long cachedAppMem = getMemLevel(CACHED_APP_MIN_ADJ);
+ outInfo.advertisedMem = getAdvertisedMem();
outInfo.availMem = getFreeMemory();
outInfo.totalMem = getTotalMemory();
outInfo.threshold = homeAppMem;
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/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index ca5bfb3..52b67b5 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -22,11 +22,11 @@
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManager.ProcessCapability;
+import static android.app.AppOpsManager.MIN_PRIORITY_UID_STATE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.UID_STATE_CACHED;
import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
import static android.app.AppOpsManager.UID_STATE_TOP;
@@ -89,7 +89,7 @@
private int getUidStateLocked(int uid) {
updateUidPendingStateIfNeeded(uid);
- return mUidStates.get(uid, UID_STATE_CACHED);
+ return mUidStates.get(uid, MIN_PRIORITY_UID_STATE);
}
@Override
@@ -191,8 +191,13 @@
int prevUidState = mUidStates.get(uid, AppOpsManager.MIN_PRIORITY_UID_STATE);
int prevCapability = mCapability.get(uid, PROCESS_CAPABILITY_NONE);
+ int pendingUidState = mPendingUidStates.get(uid, MIN_PRIORITY_UID_STATE);
+ int pendingCapability = mPendingCapability.get(uid, PROCESS_CAPABILITY_NONE);
long pendingStateCommitTime = mPendingCommitTime.get(uid, 0);
- if (uidState != prevUidState || capability != prevCapability) {
+ if ((pendingStateCommitTime == 0
+ && (uidState != prevUidState || capability != prevCapability))
+ || (pendingStateCommitTime != 0
+ && (uidState != pendingUidState || capability != pendingCapability))) {
mPendingUidStates.put(uid, uidState);
mPendingCapability.put(uid, capability);
@@ -236,7 +241,7 @@
@Override
public void dumpUidState(PrintWriter pw, int uid, long nowElapsed) {
- int state = mUidStates.get(uid, UID_STATE_CACHED);
+ int state = mUidStates.get(uid, MIN_PRIORITY_UID_STATE);
// if no pendingState set to state to suppress output
int pendingState = mPendingUidStates.get(uid, state);
pw.print(" state=");
@@ -294,11 +299,11 @@
}
private void commitUidPendingState(int uid) {
- int pendingUidState = mPendingUidStates.get(uid, UID_STATE_CACHED);
+ int pendingUidState = mPendingUidStates.get(uid, MIN_PRIORITY_UID_STATE);
int pendingCapability = mPendingCapability.get(uid, PROCESS_CAPABILITY_NONE);
boolean pendingVisibleAppWidget = mPendingVisibleAppWidget.get(uid, false);
- int uidState = mUidStates.get(uid, UID_STATE_CACHED);
+ int uidState = mUidStates.get(uid, MIN_PRIORITY_UID_STATE);
int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE);
boolean visibleAppWidget = mVisibleAppWidget.get(uid, false);
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/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/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index a6a3db1..e653f04 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -1616,6 +1616,10 @@
updateEnabled();
restartLocationRequest();
}
+
+ // Re-register network callbacks to get an update of available networks right away.
+ mNetworkConnectivityHandler.unregisterNetworkCallbacks();
+ mNetworkConnectivityHandler.registerNetworkCallbacks();
}
@Override
@@ -1722,7 +1726,8 @@
String setId = null;
int subId = SubscriptionManager.getDefaultDataSubscriptionId();
- if (mNIHandler.getInEmergency() && mNetworkConnectivityHandler.getActiveSubId() >= 0) {
+ if (mGnssConfiguration.isActiveSimEmergencySuplEnabled() && mNIHandler.getInEmergency()
+ && mNetworkConnectivityHandler.getActiveSubId() >= 0) {
subId = mNetworkConnectivityHandler.getActiveSubId();
}
if (SubscriptionManager.isValidSubscriptionId(subId)) {
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index dc1f4dd..02bdfd5 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -301,6 +301,10 @@
mConnMgr.registerNetworkCallback(networkRequest, mNetworkConnectivityCallback, mHandler);
}
+ void unregisterNetworkCallbacks() {
+ mConnMgr.unregisterNetworkCallback(mNetworkConnectivityCallback);
+ }
+
/**
* @return {@code true} if there is a data network available for outgoing connections,
* {@code false} otherwise.
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/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/updates/ConfigUpdateInstallReceiver.java b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
index 948439d..2d022ae 100644
--- a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
@@ -189,6 +189,13 @@
if (!parent.exists()) {
throw new IOException("Failed to create directory " + parent.getCanonicalPath());
}
+
+ // Give executable permissions to parent folders.
+ while (!(parent.equals(updateDir))) {
+ parent.setExecutable(true, false);
+ parent = parent.getParentFile();
+ }
+
// create the temporary file
tmp = File.createTempFile("journal", "", dir);
// mark tmp -rw-r--r--
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 8c5f053..7d9ae87 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -202,8 +202,7 @@
// target windows. But the windows still need to use sync transaction to keep the appearance
// in previous rotation, so request a no-op sync to keep the state.
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
- if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST
- && mTargetWindowTokens.valueAt(i).mAction != Operation.ACTION_SEAMLESS) {
+ if (mTargetWindowTokens.valueAt(i).canDrawBeforeStartTransaction()) {
// Expect a screenshot layer will cover the non seamless windows.
continue;
}
@@ -489,7 +488,7 @@
return false;
}
final Operation op = mTargetWindowTokens.get(w.mToken);
- if (op == null) return false;
+ if (op == null || op.canDrawBeforeStartTransaction()) return false;
if (DEBUG) Slog.d(TAG, "handleFinishDrawing " + w);
if (op.mDrawTransaction == null) {
if (w.isClientLocal()) {
@@ -554,5 +553,14 @@
Operation(@Action int action) {
mAction = action;
}
+
+ /**
+ * Returns {@code true} if the corresponding window can draw its latest content before the
+ * start transaction of rotation transition is applied.
+ */
+ boolean canDrawBeforeStartTransaction() {
+ return TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST
+ && mAction != ACTION_SEAMLESS;
+ }
}
}
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/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 7bb57d8..9e01f10 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -116,7 +116,7 @@
private boolean mWillFinishToHome = false;
private final Runnable mFailsafeRunnable = this::onFailsafe;
- // The recents component app token that is shown behind the visibile tasks
+ // The recents component app token that is shown behind the visible tasks
private ActivityRecord mTargetActivityRecord;
private DisplayContent mDisplayContent;
private int mTargetActivityType;
@@ -456,6 +456,22 @@
}
}
+ /**
+ * Return whether the given window should still be considered interesting for the all-drawn
+ * state. This is only interesting for the target app, which may have child windows that are
+ * not actually visible and should not be considered interesting and waited upon.
+ */
+ protected boolean isInterestingForAllDrawn(WindowState window) {
+ if (isTargetApp(window.getActivityRecord())) {
+ if (window.getWindowType() != TYPE_BASE_APPLICATION
+ && window.getAttrs().alpha == 0f) {
+ // If there is a cihld window that is alpha 0, then ignore that window
+ return false;
+ }
+ }
+ // By default all windows are still interesting for all drawn purposes
+ return true;
+ }
/**
* Whether a task should be filtered from the recents animation. This can be true for tasks
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..7434ea0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2042,9 +2042,13 @@
* it must be drawn before allDrawn can become true.
*/
boolean isInteresting() {
+ final RecentsAnimationController recentsAnimationController =
+ mWmService.getRecentsAnimationController();
return mActivityRecord != null && !mAppDied
&& (!mActivityRecord.isFreezingScreen() || !mAppFreezing)
- && mViewVisibility == View.VISIBLE;
+ && mViewVisibility == View.VISIBLE
+ && (recentsAnimationController == null
+ || recentsAnimationController.isInterestingForAllDrawn(this));
}
/**
@@ -4568,7 +4572,7 @@
float translateToWindowX(float x) {
float winX = x - mWindowFrames.mFrame.left;
if (mGlobalScale != 1f) {
- winX *= mGlobalScale;
+ winX *= mInvGlobalScale;
}
return winX;
}
@@ -4576,7 +4580,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/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/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 3331839..51894f3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -735,6 +735,11 @@
assertTrue(asyncRotationController.isTargetToken(decorToken));
assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true);
+ if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST) {
+ // Only seamless window syncs its draw transaction with transition.
+ assertFalse(asyncRotationController.handleFinishDrawing(statusBar, mMockT));
+ assertTrue(asyncRotationController.handleFinishDrawing(screenDecor, mMockT));
+ }
screenDecor.setOrientationChanging(false);
// Status bar finishes drawing before the start transaction. Its fade-in animation will be
// executed until the transaction is committed, so it is still in target tokens.
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
index eafcef2..1e74451 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -210,21 +210,15 @@
final int translatedAppUid =
getAppUidByComponentName(getContext(), componentName, getUserId());
final String packageName = componentName.getPackageName();
- if (activityDestroyed) {
- // In the Activity destroy case, we only calls onTranslationFinished() in
- // non-finisTranslation() state. If there is a finisTranslation() calls by apps, we
- // should remove the waiting callback to avoid callback twice.
+ // In the Activity destroyed case, we only call onTranslationFinished() in
+ // non-finishTranslation() state. If there is a finishTranslation() call by apps, we
+ // should remove the waiting callback to avoid invoking callbacks twice.
+ if (activityDestroyed || mWaitingFinishedCallbackActivities.contains(token)) {
invokeCallbacks(STATE_UI_TRANSLATION_FINISHED,
/* sourceSpec= */ null, /* targetSpec= */ null,
packageName, translatedAppUid);
mWaitingFinishedCallbackActivities.remove(token);
- } else {
- if (mWaitingFinishedCallbackActivities.contains(token)) {
- invokeCallbacks(STATE_UI_TRANSLATION_FINISHED,
- /* sourceSpec= */ null, /* targetSpec= */ null,
- packageName, translatedAppUid);
- mWaitingFinishedCallbackActivities.remove(token);
- }
+ mActiveTranslations.remove(token);
}
}
@@ -237,6 +231,9 @@
// Activity is the new Activity, the original Activity is paused in the same task.
// To make sure the operation still work, we use the token to find the target Activity in
// this task, not the top Activity only.
+ //
+ // Note: getAttachedNonFinishingActivityForTask() takes the shareable activity token. We
+ // call this method so that we can get the regular activity token below.
ActivityTokens candidateActivityTokens =
mActivityTaskManagerInternal.getAttachedNonFinishingActivityForTask(taskId, token);
if (candidateActivityTokens == null) {
@@ -263,27 +260,27 @@
getAppUidByComponentName(getContext(), componentName, getUserId());
String packageName = componentName.getPackageName();
- invokeCallbacksIfNecessaryLocked(state, sourceSpec, targetSpec, packageName, activityToken,
+ invokeCallbacksIfNecessaryLocked(state, sourceSpec, targetSpec, packageName, token,
translatedAppUid);
- updateActiveTranslationsLocked(state, sourceSpec, targetSpec, packageName, activityToken,
+ updateActiveTranslationsLocked(state, sourceSpec, targetSpec, packageName, token,
translatedAppUid);
}
@GuardedBy("mLock")
private void updateActiveTranslationsLocked(int state, TranslationSpec sourceSpec,
- TranslationSpec targetSpec, String packageName, IBinder activityToken,
+ TranslationSpec targetSpec, String packageName, IBinder shareableActivityToken,
int translatedAppUid) {
// We keep track of active translations and their state so that we can:
// 1. Trigger callbacks that are registered after translation has started.
// See registerUiTranslationStateCallbackLocked().
// 2. NOT trigger callbacks when the state didn't change.
// See invokeCallbacksIfNecessaryLocked().
- ActiveTranslation activeTranslation = mActiveTranslations.get(activityToken);
+ ActiveTranslation activeTranslation = mActiveTranslations.get(shareableActivityToken);
switch (state) {
case STATE_UI_TRANSLATION_STARTED: {
if (activeTranslation == null) {
try {
- activityToken.linkToDeath(this, /* flags= */ 0);
+ shareableActivityToken.linkToDeath(this, /* flags= */ 0);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call linkToDeath for translated app with uid="
+ translatedAppUid + "; activity is already dead", e);
@@ -294,7 +291,7 @@
packageName, translatedAppUid);
return;
}
- mActiveTranslations.put(activityToken,
+ mActiveTranslations.put(shareableActivityToken,
new ActiveTranslation(sourceSpec, targetSpec, translatedAppUid,
packageName));
}
@@ -317,7 +314,7 @@
case STATE_UI_TRANSLATION_FINISHED: {
if (activeTranslation != null) {
- mActiveTranslations.remove(activityToken);
+ mActiveTranslations.remove(shareableActivityToken);
}
break;
}
@@ -332,12 +329,12 @@
@GuardedBy("mLock")
private void invokeCallbacksIfNecessaryLocked(int state, TranslationSpec sourceSpec,
- TranslationSpec targetSpec, String packageName, IBinder activityToken,
+ TranslationSpec targetSpec, String packageName, IBinder shareableActivityToken,
int translatedAppUid) {
boolean shouldInvokeCallbacks = true;
int stateForCallbackInvocation = state;
- ActiveTranslation activeTranslation = mActiveTranslations.get(activityToken);
+ ActiveTranslation activeTranslation = mActiveTranslations.get(shareableActivityToken);
if (activeTranslation == null) {
if (state != STATE_UI_TRANSLATION_STARTED) {
shouldInvokeCallbacks = false;
@@ -403,14 +400,6 @@
}
}
- if (DEBUG) {
- Slog.d(TAG,
- (shouldInvokeCallbacks ? "" : "NOT ")
- + "Invoking callbacks for translation state="
- + stateForCallbackInvocation + " for app with uid=" + translatedAppUid
- + " packageName=" + packageName);
- }
-
if (shouldInvokeCallbacks) {
invokeCallbacks(stateForCallbackInvocation, sourceSpec, targetSpec, packageName,
translatedAppUid);
@@ -448,7 +437,7 @@
pw.println(waitingFinishCallbackSize);
for (IBinder activityToken : mWaitingFinishedCallbackActivities) {
pw.print(prefix);
- pw.print("activityToken: ");
+ pw.print("shareableActivityToken: ");
pw.println(activityToken);
}
}
@@ -458,7 +447,14 @@
int state, TranslationSpec sourceSpec, TranslationSpec targetSpec, String packageName,
int translatedAppUid) {
Bundle result = createResultForCallback(state, sourceSpec, targetSpec, packageName);
- if (mCallbacks.getRegisteredCallbackCount() == 0) {
+ int registeredCallbackCount = mCallbacks.getRegisteredCallbackCount();
+ if (DEBUG) {
+ Slog.d(TAG, "Invoking " + registeredCallbackCount + " callbacks for translation state="
+ + state + " for app with uid=" + translatedAppUid
+ + " packageName=" + packageName);
+ }
+
+ if (registeredCallbackCount == 0) {
return;
}
List<InputMethodInfo> enabledInputMethods = getEnabledInputMethods();
@@ -521,8 +517,10 @@
@GuardedBy("mLock")
public void registerUiTranslationStateCallbackLocked(IRemoteCallback callback, int sourceUid) {
mCallbacks.register(callback, sourceUid);
-
- if (mActiveTranslations.size() == 0) {
+ int numActiveTranslations = mActiveTranslations.size();
+ Slog.i(TAG, "New registered callback for sourceUid=" + sourceUid + " with currently "
+ + numActiveTranslations + " active translations");
+ if (numActiveTranslations == 0) {
return;
}
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