Merge "Enlarge mLast4FrameMetricsInfos from 4 to 6 entries" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index baf142a..c7df662 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9875,6 +9875,7 @@
field public static final int RESULT_DISCOVERY_TIMEOUT = 2; // 0x2
field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
field public static final int RESULT_OK = -1; // 0xffffffff
+ field @FlaggedApi("android.companion.association_failure_code") public static final int RESULT_SECURITY_ERROR = 4; // 0x4
field public static final int RESULT_USER_REJECTED = 1; // 0x1
}
@@ -9884,7 +9885,7 @@
method public void onAssociationPending(@NonNull android.content.IntentSender);
method @Deprecated public void onDeviceFound(@NonNull android.content.IntentSender);
method public abstract void onFailure(@Nullable CharSequence);
- method @FlaggedApi("android.companion.association_failure_code") public void onFailure(int);
+ method @FlaggedApi("android.companion.association_failure_code") public void onFailure(int, @Nullable CharSequence);
}
public abstract class CompanionDeviceService extends android.app.Service {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 965e3c4..ba1dc56 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4334,11 +4334,13 @@
*/
@RequiresPermission(value = MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional = true)
@FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+ @UserHandleAware
public @ContentProtectionPolicy int getContentProtectionPolicy(@Nullable ComponentName admin) {
throwIfParentInstance("getContentProtectionPolicy");
if (mService != null) {
try {
- return mService.getContentProtectionPolicy(admin, mContext.getPackageName());
+ return mService.getContentProtectionPolicy(admin, mContext.getPackageName(),
+ myUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index c393a9e..d4e5c99 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -621,7 +621,7 @@
void calculateHasIncompatibleAccounts();
void setContentProtectionPolicy(in ComponentName who, String callerPackageName, int policy);
- int getContentProtectionPolicy(in ComponentName who, String callerPackageName);
+ int getContentProtectionPolicy(in ComponentName who, String callerPackageName, int userId);
int[] getSubscriptionIds(String callerPackageName);
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
index a50425e..db3de62 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
@@ -40,8 +40,8 @@
public ExecuteAppFunctionRequest createFromParcel(Parcel parcel) {
String targetPackageName = parcel.readString8();
String functionIdentifier = parcel.readString8();
- GenericDocument parameters;
- parameters = GenericDocument.createFromParcel(parcel);
+ GenericDocumentWrapper parameters = GenericDocumentWrapper
+ .CREATOR.createFromParcel(parcel);
Bundle extras = parcel.readBundle(Bundle.class.getClassLoader());
return new ExecuteAppFunctionRequest(
targetPackageName, functionIdentifier, extras, parameters);
@@ -75,17 +75,17 @@
*
* <p>The document may have missing parameters. Developers are advised to implement defensive
* handling measures.
- *
+ * <p>
* TODO(b/357551503): Document how function parameters can be obtained for function execution
*/
@NonNull
- private final GenericDocument mParameters;
+ private final GenericDocumentWrapper mParameters;
private ExecuteAppFunctionRequest(
@NonNull String targetPackageName,
@NonNull String functionIdentifier,
@NonNull Bundle extras,
- @NonNull GenericDocument parameters) {
+ @NonNull GenericDocumentWrapper parameters) {
mTargetPackageName = Objects.requireNonNull(targetPackageName);
mFunctionIdentifier = Objects.requireNonNull(functionIdentifier);
mExtras = Objects.requireNonNull(extras);
@@ -117,7 +117,7 @@
*/
@NonNull
public GenericDocument getParameters() {
- return mParameters;
+ return mParameters.getValue();
}
/**
@@ -152,7 +152,8 @@
@NonNull
private Bundle mExtras = Bundle.EMPTY;
@NonNull
- private GenericDocument mParameters = new GenericDocument.Builder<>("", "", "").build();
+ private GenericDocument mParameters =
+ new GenericDocument.Builder<>("", "", "").build();
public Builder(@NonNull String targetPackageName, @NonNull String functionIdentifier) {
mTargetPackageName = Objects.requireNonNull(targetPackageName);
@@ -173,7 +174,8 @@
*/
@NonNull
public Builder setParameters(@NonNull GenericDocument parameters) {
- mParameters = Objects.requireNonNull(parameters);
+ Objects.requireNonNull(parameters);
+ mParameters = parameters;
return this;
}
@@ -183,7 +185,8 @@
@NonNull
public ExecuteAppFunctionRequest build() {
return new ExecuteAppFunctionRequest(
- mTargetPackageName, mFunctionIdentifier, mExtras, mParameters);
+ mTargetPackageName, mFunctionIdentifier, mExtras,
+ new GenericDocumentWrapper(mParameters));
}
}
}
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index 872327d..9fb3375 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -41,13 +41,16 @@
new Creator<ExecuteAppFunctionResponse>() {
@Override
public ExecuteAppFunctionResponse createFromParcel(Parcel parcel) {
- GenericDocument result =
- Objects.requireNonNull(GenericDocument.createFromParcel(parcel));
+ GenericDocumentWrapper resultWrapper =
+ Objects.requireNonNull(
+ GenericDocumentWrapper
+ .CREATOR.createFromParcel(parcel));
Bundle extras = Objects.requireNonNull(
parcel.readBundle(Bundle.class.getClassLoader()));
int resultCode = parcel.readInt();
String errorMessage = parcel.readString8();
- return new ExecuteAppFunctionResponse(result, extras, resultCode, errorMessage);
+ return new ExecuteAppFunctionResponse(
+ resultWrapper, extras, resultCode, errorMessage);
}
@Override
@@ -127,7 +130,7 @@
* <p>See {@link #getResultDocument} for more information on extracting the return value.
*/
@NonNull
- private final GenericDocument mResultDocument;
+ private final GenericDocumentWrapper mResultDocumentWrapper;
/**
* Returns the additional metadata data relevant to this function execution response.
@@ -135,17 +138,30 @@
@NonNull
private final Bundle mExtras;
- private ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument,
+ private ExecuteAppFunctionResponse(@NonNull GenericDocumentWrapper resultDocumentWrapper,
@NonNull Bundle extras,
@ResultCode int resultCode,
@Nullable String errorMessage) {
- mResultDocument = Objects.requireNonNull(resultDocument);
+ mResultDocumentWrapper = Objects.requireNonNull(resultDocumentWrapper);
mExtras = Objects.requireNonNull(extras);
mResultCode = resultCode;
mErrorMessage = errorMessage;
}
/**
+ * Returns result codes from throwable.
+ *
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+ static @ResultCode int getResultCode(@NonNull Throwable t) {
+ if (t instanceof IllegalArgumentException) {
+ return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
+ }
+ return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
+ }
+
+ /**
* Returns a generic document containing the return value of the executed function.
*
* <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.</p>
@@ -166,7 +182,7 @@
*/
@NonNull
public GenericDocument getResultDocument() {
- return mResultDocument;
+ return mResultDocumentWrapper.getValue();
}
/**
@@ -210,7 +226,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- mResultDocument.writeToParcel(dest, flags);
+ mResultDocumentWrapper.writeToParcel(dest, flags);
dest.writeBundle(mExtras);
dest.writeInt(mResultCode);
dest.writeString8(mErrorMessage);
@@ -236,24 +252,13 @@
}
/**
- * Returns result codes from throwable.
- *
- * @hide
- */
- static @ResultCode int getResultCode(@NonNull Throwable t) {
- if (t instanceof IllegalArgumentException) {
- return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
- }
- return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
- }
-
- /**
* The builder for creating {@link ExecuteAppFunctionResponse} instances.
*/
@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
public static final class Builder {
@NonNull
- private GenericDocument mResultDocument = new GenericDocument.Builder<>("", "", "").build();
+ private GenericDocument mResultDocument =
+ new GenericDocument.Builder<>("", "", "").build();
@NonNull
private Bundle mExtras = Bundle.EMPTY;
private int mResultCode;
@@ -271,7 +276,8 @@
* with a result code of {@link #RESULT_OK} and a resultDocument.
*/
public Builder(@NonNull GenericDocument resultDocument) {
- mResultDocument = Objects.requireNonNull(resultDocument);
+ Objects.requireNonNull(resultDocument);
+ mResultDocument = resultDocument;
mResultCode = RESULT_OK;
}
@@ -300,7 +306,8 @@
@NonNull
public ExecuteAppFunctionResponse build() {
return new ExecuteAppFunctionResponse(
- mResultDocument, mExtras, mResultCode, mErrorMessage);
+ new GenericDocumentWrapper(mResultDocument),
+ mExtras, mResultCode, mErrorMessage);
}
}
}
diff --git a/core/java/android/app/appfunctions/GenericDocumentWrapper.java b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
new file mode 100644
index 0000000..8c76c8e
--- /dev/null
+++ b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 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.app.appfunctions;
+
+import android.app.appsearch.GenericDocument;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * The Parcelable object contains a {@link GenericDocument} to allow the parcelization of it
+ * exceeding the binder limit.
+ *
+ * <p>{#link {@link Parcel#writeBlob(byte[])}} could take care of whether to pass data via binder
+ * directly or Android shared memory if the data is large.
+ *
+ * @hide
+ * @see Parcel#writeBlob(byte[])
+ */
+public final class GenericDocumentWrapper implements Parcelable {
+ public static final Creator<GenericDocumentWrapper> CREATOR =
+ new Creator<>() {
+ @Override
+ public GenericDocumentWrapper createFromParcel(Parcel in) {
+ byte[] dataBlob = Objects.requireNonNull(in.readBlob());
+ Parcel unmarshallParcel = Parcel.obtain();
+ try {
+ unmarshallParcel.unmarshall(dataBlob, 0, dataBlob.length);
+ unmarshallParcel.setDataPosition(0);
+ return new GenericDocumentWrapper(
+ GenericDocument.createFromParcel(unmarshallParcel));
+ } finally {
+ unmarshallParcel.recycle();
+ }
+ }
+
+ @Override
+ public GenericDocumentWrapper[] newArray(int size) {
+ return new GenericDocumentWrapper[size];
+ }
+ };
+ @NonNull
+ private final GenericDocument mGenericDocument;
+
+ public GenericDocumentWrapper(@NonNull GenericDocument genericDocument) {
+ mGenericDocument = Objects.requireNonNull(genericDocument);
+ }
+
+ /**
+ * Returns the wrapped {@link android.app.appsearch.GenericDocument}
+ */
+ @NonNull
+ public GenericDocument getValue() {
+ return mGenericDocument;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Parcel parcel = Parcel.obtain();
+ try {
+ mGenericDocument.writeToParcel(parcel, flags);
+ byte[] bytes = parcel.marshall();
+ dest.writeBlob(bytes);
+ } finally {
+ parcel.recycle();
+ }
+
+ }
+}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 1529842..1cdf3b1 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -21,7 +21,6 @@
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER;
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH;
-import static java.util.Collections.unmodifiableMap;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
@@ -58,7 +57,6 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
-import android.util.ArrayMap;
import android.util.ExceptionUtils;
import android.util.Log;
import android.util.SparseArray;
@@ -78,7 +76,6 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
@@ -146,12 +143,19 @@
/**
* The result code to propagate back to the user activity, indicates the internal error
* in CompanionDeviceManager.
- * E.g. Missing necessary permissions or duplicate {@link AssociationRequest}s when create the
- * {@link AssociationInfo}.
*/
public static final int RESULT_INTERNAL_ERROR = 3;
/**
+ * The result code to propagate back to the user activity and
+ * {@link Callback#onFailure(int, CharSequence)}, indicates app is not allow to create the
+ * association due to the security issue.
+ * E.g. There are missing necessary permissions when creating association.
+ */
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_FAILURE_CODE)
+ public static final int RESULT_SECURITY_ERROR = 4;
+
+ /**
* Requesting applications will receive the String in {@link Callback#onFailure} if the
* association dialog is explicitly declined by the users. E.g. press the Don't allow
* button.
@@ -374,7 +378,6 @@
*/
public void onAssociationCreated(@NonNull AssociationInfo associationInfo) {}
- //TODO(b/331459560): Add deprecated and remove abstract after API cut for W.
/**
* Invoked if the association could not be created.
*
@@ -385,11 +388,15 @@
/**
* Invoked if the association could not be created.
*
- * @param resultCode indicate the particular reason why the association
- * could not be created.
+ * Please note that both {@link #onFailure(CharSequence error)} and this
+ * API will be called if the association could not be created.
+ *
+ * @param errorCode indicate the particular error code why the association
+ * could not be created.
+ * @param error error message.
*/
@FlaggedApi(Flags.FLAG_ASSOCIATION_FAILURE_CODE)
- public void onFailure(@ResultCode int resultCode) {}
+ public void onFailure(@ResultCode int errorCode, @Nullable CharSequence error) {}
}
private final ICompanionDeviceManager mService;
@@ -1825,12 +1832,12 @@
}
@Override
- public void onFailure(@ResultCode int resultCode) {
+ public void onFailure(@ResultCode int errorCode, @Nullable CharSequence error) {
if (Flags.associationFailureCode()) {
- execute(mCallback::onFailure, resultCode);
+ execute(mCallback::onFailure, errorCode, error);
}
- execute(mCallback::onFailure, RESULT_CODE_TO_REASON.get(resultCode));
+ execute(mCallback::onFailure, error);
}
private <T> void execute(Consumer<T> callback, T arg) {
@@ -1840,6 +1847,12 @@
mHandler.post(() -> callback.accept(arg));
}
}
+
+ private <T, U> void execute(BiConsumer<T, U> callback, T arg1, U arg2) {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> callback.accept(arg1, arg2));
+ }
+ }
}
private static class OnAssociationsChangedListenerProxy
@@ -2014,15 +2027,4 @@
}
}
}
-
- private static final Map<Integer, String> RESULT_CODE_TO_REASON;
- static {
- final Map<Integer, String> map = new ArrayMap<>();
- map.put(RESULT_CANCELED, REASON_CANCELED);
- map.put(RESULT_USER_REJECTED, REASON_USER_REJECTED);
- map.put(RESULT_DISCOVERY_TIMEOUT, REASON_DISCOVERY_TIMEOUT);
- map.put(RESULT_INTERNAL_ERROR, REASON_INTERNAL_ERROR);
-
- RESULT_CODE_TO_REASON = unmodifiableMap(map);
- }
}
diff --git a/core/java/android/companion/IAssociationRequestCallback.aidl b/core/java/android/companion/IAssociationRequestCallback.aidl
index b1be30a..a6f86a5 100644
--- a/core/java/android/companion/IAssociationRequestCallback.aidl
+++ b/core/java/android/companion/IAssociationRequestCallback.aidl
@@ -25,5 +25,5 @@
oneway void onAssociationCreated(in AssociationInfo associationInfo);
- oneway void onFailure(in int resultCode);
+ oneway void onFailure(in int errorCode, in CharSequence error);
}
\ No newline at end of file
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index cd3ce87..5779a44 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -2501,33 +2501,19 @@
return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true, indent);
}
- private void addIndentOrComma(StringBuilder sb, String indent) {
- if (indent != null) {
- sb.append("\n ");
- sb.append(indent);
- } else {
- sb.append(", ");
- }
+ /** @hide */
+ public String toSimpleString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(mId);
+ addReadableFlags(sb);
+ return sb.toString();
}
- private String toStringInner(boolean secure, boolean includeInternalData, String indent) {
- final StringBuilder sb = new StringBuilder();
-
- if (indent != null) {
- sb.append(indent);
- }
-
- sb.append("ShortcutInfo {");
-
- sb.append("id=");
- sb.append(secure ? "***" : mId);
-
- sb.append(", flags=0x");
- sb.append(Integer.toHexString(mFlags));
+ private void addReadableFlags(StringBuilder sb) {
sb.append(" [");
if ((mFlags & FLAG_SHADOW) != 0) {
- // Note the shadow flag isn't actually used anywhere and it's just for dumpsys, so
- // we don't have an isXxx for this.
+ // Note the shadow flag isn't actually used anywhere and it's
+ // just for dumpsys, so we don't have an isXxx for this.
sb.append("Sdw");
}
if (!isEnabled()) {
@@ -2576,7 +2562,32 @@
sb.append("Hid-L");
}
sb.append("]");
+ }
+ private void addIndentOrComma(StringBuilder sb, String indent) {
+ if (indent != null) {
+ sb.append("\n ");
+ sb.append(indent);
+ } else {
+ sb.append(", ");
+ }
+ }
+
+ private String toStringInner(boolean secure, boolean includeInternalData, String indent) {
+ final StringBuilder sb = new StringBuilder();
+
+ if (indent != null) {
+ sb.append(indent);
+ }
+
+ sb.append("ShortcutInfo {");
+
+ sb.append("id=");
+ sb.append(secure ? "***" : mId);
+
+ sb.append(", flags=0x");
+ sb.append(Integer.toHexString(mFlags));
+ addReadableFlags(sb);
addIndentOrComma(sb, indent);
sb.append("packageName=");
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 7665fe8..04a810a 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -461,6 +461,12 @@
@SuppressLint("NonUserGetterCalled")
public boolean registerClient(Context ctx, IBinder token, int extension,
String cameraId, Map<String, CameraMetadataNative> characteristicsMapNative) {
+ if (!SystemProperties.getBoolean("ro.camerax.extensions.enabled",
+ /*default*/ false)) {
+ Log.v(TAG, "Disabled camera extension property!");
+ return false;
+ }
+
boolean ret = registerClientHelper(ctx, token, extension, false /*useFallback*/);
if (Flags.concertMode()) {
diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS
index 8bd6c85..c38ee08 100644
--- a/core/java/android/security/OWNERS
+++ b/core/java/android/security/OWNERS
@@ -3,6 +3,7 @@
[email protected]
[email protected]
[email protected]
[email protected]
per-file *NetworkSecurityPolicy.java = file:net/OWNERS
per-file Confirmation*.java = file:/keystore/OWNERS
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 1734223..ad457ce 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -25,6 +25,7 @@
import static android.graphics.Matrix.MSKEW_Y;
import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.flags.Flags.disableDrawWakeLock;
import static com.android.window.flags.Flags.FLAG_OFFLOAD_COLOR_EXTRACTION;
import static com.android.window.flags.Flags.noDuplicateSurfaceDestroyedEvents;
@@ -51,6 +52,7 @@
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
@@ -208,6 +210,15 @@
private boolean mIsWearOs;
/**
+ * This change disables the {@code DRAW_WAKE_LOCK}, an internal wakelock acquired per-frame
+ * duration display DOZE. It was added to allow animation during AOD. This wakelock consumes
+ * battery severely if the animation is too heavy, so, it will be removed.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ private static final long DISABLE_DRAW_WAKE_LOCK_WALLPAPER = 361433696L;
+
+ /**
* Wear products currently force a slight scaling transition to wallpapers
* when the QSS is opened. However, on Wear 6 (SDK 35) and above, 1P watch faces
* will be expected to either implement their own scaling, or to override this
@@ -362,6 +373,8 @@
private SurfaceControl mScreenshotSurfaceControl;
private Point mScreenshotSize = new Point();
+ private final boolean mDisableDrawWakeLock;
+
final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() {
{
mRequestedFormat = PixelFormat.RGBX_8888;
@@ -406,6 +419,9 @@
}
private void prepareToDraw() {
+ if (mDisableDrawWakeLock) {
+ return;
+ }
if (mDisplayState == Display.STATE_DOZE) {
try {
mSession.pokeDrawLock(mWindow);
@@ -546,6 +562,8 @@
public Engine(Supplier<Long> clockFunction, Handler handler) {
mClockFunction = clockFunction;
mHandler = handler;
+ mDisableDrawWakeLock = CompatChanges.isChangeEnabled(DISABLE_DRAW_WAKE_LOCK_WALLPAPER)
+ && disableDrawWakeLock();
}
/**
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 1535145..815fd1c 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -556,12 +556,9 @@
+ "is a different type from the others. All frames should be the "
+ "same type.");
}
- if (drawableFrame.getIntrinsicWidth() != width ||
- drawableFrame.getIntrinsicHeight() != height) {
- throw new IllegalArgumentException("The bitmap size of " + i + "-th frame "
- + "is different. All frames should have the exact same size and "
- + "share the same hotspot.");
- }
+ // TODO(b/361232935): Check when bitmap size of the ith frame is different
+ // drawableFrame.getIntrinsicWidth() != width ||
+ // drawableFrame.getIntrinsicHeight() != height
if (isVectorAnimation) {
drawableFrame = getBitmapDrawableFromVectorDrawable(resources,
(VectorDrawable) drawableFrame, pointerScale);
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 1922327..eb35817 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -83,7 +83,6 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.StrictMode;
-import android.os.Trace;
import android.os.UserHandle;
import android.system.Os;
import android.text.TextUtils;
@@ -6247,18 +6246,6 @@
private View inflateView(Context context, RemoteViews rv, @Nullable ViewGroup parent,
@StyleRes int applyThemeResId, @Nullable ColorResources colorResources) {
- try {
- Trace.beginSection(rv.hasDrawInstructions()
- ? "RemoteViews#inflateViewWithDrawInstructions"
- : "RemoteViews#inflateView");
- return inflateViewInternal(context, rv, parent, applyThemeResId, colorResources);
- } finally {
- Trace.endSection();
- }
- }
-
- private View inflateViewInternal(Context context, RemoteViews rv, @Nullable ViewGroup parent,
- @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) {
// RemoteViews may be built by an application installed in another
// user. So build a context that loads resources from that user but
// still returns the current users userId so settings like data / time formats
@@ -6397,7 +6384,7 @@
private View mResult;
private ViewTree mTree;
- private List<Action> mActions;
+ private Action[] mActions;
private Exception mError;
private AsyncApplyTask(
@@ -6424,20 +6411,11 @@
if (mRV.mActions != null) {
int count = mRV.mActions.size();
- mActions = new ArrayList<>(count);
- try {
- Trace.beginSection(hasDrawInstructions()
- ? "RemoteViews#initActionAsyncWithDrawInstructions"
- : "RemoteViews#initActionAsync");
- for (Action action : mRV.mActions) {
- if (isCancelled()) {
- break;
- }
- // TODO: check if isCancelled in nested views.
- mActions.add(action.initActionAsync(mTree, mParent, mApplyParams));
- }
- } finally {
- Trace.endSection();
+ mActions = new Action[count];
+ for (int i = 0; i < count && !isCancelled(); i++) {
+ // TODO: check if isCancelled in nested views.
+ mActions[i] = mRV.mActions.get(i)
+ .initActionAsync(mTree, mParent, mApplyParams);
}
} else {
mActions = null;
@@ -6459,7 +6437,14 @@
try {
if (mActions != null) {
- mRV.performApply(viewTree.mRoot, mParent, mApplyParams, mActions);
+
+ ActionApplyParams applyParams = mApplyParams.clone();
+ if (applyParams.handler == null) {
+ applyParams.handler = DEFAULT_INTERACTION_HANDLER;
+ }
+ for (Action a : mActions) {
+ a.apply(viewTree.mRoot, mParent, applyParams);
+ }
}
// If the parent of the view is has is a root, resolve the recycling.
if (mTopLevel && mResult instanceof ViewGroup) {
@@ -6635,11 +6620,6 @@
}
private void performApply(View v, ViewGroup parent, ActionApplyParams params) {
- performApply(v, parent, params, mActions);
- }
-
- private void performApply(
- View v, ViewGroup parent, ActionApplyParams params, List<Action> actions) {
params = params.clone();
if (params.handler == null) {
params.handler = DEFAULT_INTERACTION_HANDLER;
@@ -6650,15 +6630,8 @@
}
if (mActions != null) {
final int count = mActions.size();
- try {
- Trace.beginSection(hasDrawInstructions()
- ? "RemoteViews#applyActionsWithDrawInstructions"
- : "RemoteViews#applyActions");
- for (int i = 0; i < count; i++) {
- mActions.get(i).apply(v, parent, params);
- }
- } finally {
- Trace.endSection();
+ for (int i = 0; i < count; i++) {
+ mActions.get(i).apply(v, parent, params);
}
}
}
diff --git a/core/java/com/android/internal/accessibility/TEST_MAPPING b/core/java/com/android/internal/accessibility/TEST_MAPPING
index 1c67399..b2b3041 100644
--- a/core/java/com/android/internal/accessibility/TEST_MAPPING
+++ b/core/java/com/android/internal/accessibility/TEST_MAPPING
@@ -2,6 +2,9 @@
"imports": [
{
"path": "frameworks/base/services/accessibility/TEST_MAPPING"
+ },
+ {
+ "path": "frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/TEST_MAPPING"
}
]
}
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 7bfb800..1204ef3 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -218,8 +218,25 @@
*/
public static final int CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE = 117;
+ /**
+ * Track attempting to snap resize a desktop window via button or drag.
+ *
+ * <p>CUJ has 3 different tags:
+ * <ul>
+ * <li>snap resizing resizable apps via maximize menu button: maximize_menu_resizable </li>
+ * <li>snap resizing resizable via drag: drag_resizable </li>
+ * <li>snap resizing non-resizable via drag: drag_non_resizable </li>
+ * </ul>
+ *
+ * <p>For non-resizable apps, the desktop window won't actually be resized, instead will return
+ * to its pre-dragged position. Attempting to snap resize a non-resizable app via the
+ * maximize menu will just result in no change, and a toast explaining the app can't be resized.
+ *
+ */
+ public static final int CUJ_DESKTOP_MODE_SNAP_RESIZE = 118;
+
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
- @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE;
+ @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_SNAP_RESIZE;
/** @hide */
@IntDef({
@@ -328,7 +345,8 @@
CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE,
CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH,
CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE,
- CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE
+ CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE,
+ CUJ_DESKTOP_MODE_SNAP_RESIZE
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {}
@@ -448,6 +466,7 @@
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_SNAP_RESIZE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_SNAP_RESIZE;
}
private Cuj() {
@@ -678,6 +697,8 @@
return "DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE";
case CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE:
return "DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE";
+ case CUJ_DESKTOP_MODE_SNAP_RESIZE:
+ return "DESKTOP_MODE_SNAP_RESIZE";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/os/FuseAppLoop.java b/core/java/com/android/internal/os/FuseAppLoop.java
index 1c6c6a7..656d4c7 100644
--- a/core/java/com/android/internal/os/FuseAppLoop.java
+++ b/core/java/com/android/internal/os/FuseAppLoop.java
@@ -211,6 +211,7 @@
if (mInstance != 0) {
native_replySimple(mInstance, unique, FUSE_OK);
}
+ mCallbackMap.remove(checkInode(inode));
mBytesMap.stopUsing(inode);
recycleLocked(args);
}
diff --git a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
index 2feb3d5..8771cde 100644
--- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
@@ -29,6 +29,7 @@
import static com.android.internal.protolog.ProtoLogMessage.SINT64_PARAMS;
import static com.android.internal.protolog.ProtoLogMessage.STR_PARAMS;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.ShellCommand;
import android.os.SystemClock;
@@ -49,6 +50,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
@@ -419,6 +421,12 @@
return group.isLogToLogcat() || (group.isLogToProto() && isProtoEnabled());
}
+ @Override
+ @NonNull
+ public List<IProtoLogGroup> getRegisteredGroups() {
+ return mLogGroups.values().stream().toList();
+ }
+
public void registerGroups(IProtoLogGroup... protoLogGroups) {
for (IProtoLogGroup group : protoLogGroups) {
mLogGroups.put(group.name(), group);
diff --git a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
index e8d5195..b82c660 100644
--- a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
@@ -18,6 +18,7 @@
import static com.android.internal.protolog.ProtoLog.REQUIRE_PROTOLOGTOOL;
+import android.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
@@ -26,6 +27,9 @@
import com.android.internal.protolog.common.IProtoLogGroup;
import com.android.internal.protolog.common.LogLevel;
+import java.util.Collections;
+import java.util.List;
+
/**
* Class only create and used to server temporarily for when there is source code pre-processing by
* the ProtoLog tool, when the tracing to Perfetto flag is off, and the static REQUIRE_PROTOLOGTOOL
@@ -79,4 +83,10 @@
public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
return true;
}
+
+ @Override
+ @NonNull
+ public List<IProtoLogGroup> getRegisteredGroups() {
+ return Collections.emptyList();
+ }
}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 49ed55d..5517967 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -306,6 +306,12 @@
|| group.isLogToLogcat();
}
+ @Override
+ @NonNull
+ public List<IProtoLogGroup> getRegisteredGroups() {
+ return mLogGroups.values().stream().toList();
+ }
+
private void registerGroupsLocally(@NonNull IProtoLogGroup[] protoLogGroups) {
final var groupsLoggingToLogcat = new ArrayList<String>();
for (IProtoLogGroup protoLogGroup : protoLogGroups) {
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index f9b9894..660d3c9 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -20,6 +20,9 @@
import com.android.internal.protolog.common.IProtoLogGroup;
import com.android.internal.protolog.common.LogLevel;
+import java.util.ArrayList;
+import java.util.Arrays;
+
/**
* ProtoLog API - exposes static logging methods. Usage of this API is similar
* to {@code android.utils.Log} class. Instead of plain text log messages each call consists of
@@ -49,6 +52,8 @@
private static IProtoLog sProtoLogInstance;
+ private static final Object sInitLock = new Object();
+
/**
* Initialize ProtoLog in this process.
* <p>
@@ -59,7 +64,17 @@
*/
public static void init(IProtoLogGroup... groups) {
if (android.tracing.Flags.perfettoProtologTracing()) {
- sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+ synchronized (sInitLock) {
+ if (sProtoLogInstance != null) {
+ // The ProtoLog instance has already been initialized in this process
+ final var alreadyRegisteredGroups = sProtoLogInstance.getRegisteredGroups();
+ final var allGroups = new ArrayList<>(alreadyRegisteredGroups);
+ allGroups.addAll(Arrays.stream(groups).toList());
+ groups = allGroups.toArray(new IProtoLogGroup[0]);
+ }
+
+ sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+ }
} else {
// The first call to ProtoLog is likely to flip REQUIRE_PROTOLOGTOOL, which is when this
// static block will be executed before REQUIRE_PROTOLOGTOOL is actually set.
diff --git a/core/java/com/android/internal/protolog/common/IProtoLog.java b/core/java/com/android/internal/protolog/common/IProtoLog.java
index d5c2ac1..f06f08a 100644
--- a/core/java/com/android/internal/protolog/common/IProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/IProtoLog.java
@@ -16,6 +16,8 @@
package com.android.internal.protolog.common;
+import java.util.List;
+
/**
* Interface for ProtoLog implementations.
*/
@@ -68,4 +70,9 @@
* @return If we need to log this group and level to either ProtoLog or Logcat.
*/
boolean isEnabled(IProtoLogGroup group, LogLevel level);
+
+ /**
+ * @return an immutable list of the registered ProtoLog groups in this ProtoLog instance.
+ */
+ List<IProtoLogGroup> getRegisteredGroups();
}
diff --git a/core/res/OWNERS b/core/res/OWNERS
index b2b58d5..5293131 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -78,6 +78,11 @@
per-file res/values/config_telephony.xml = file:/platform/frameworks/opt/telephony:/OWNERS
per-file res/xml/sms_short_codes.xml = file:/platform/frameworks/opt/telephony:/OWNERS
+# Input Method Framework
+per-file res/*/*input_method* = file:/services/core/java/com/android/server/inputmethod/OWNERS
+per-file res/*/*_ime* = file:/services/core/java/com/android/server/inputmethod/OWNERS
+per-file res/*/ime_* = file:/services/core/java/com/android/server/inputmethod/OWNERS
+
# TV Input Framework
per-file res/values/config_tv_external_input_logging.xml = file:/services/core/java/com/android/server/tv/OWNERS
diff --git a/core/res/res/color/input_method_switch_on_item.xml b/core/res/res/color/input_method_switch_on_item.xml
new file mode 100644
index 0000000..49fe081
--- /dev/null
+++ b/core/res/res/color/input_method_switch_on_item.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_activated="true" android:color="?attr/materialColorOnSecondaryContainer" />
+ <item android:color="?attr/materialColorOnSurface" />
+</selector>
diff --git a/core/res/res/layout/input_method_switch_item_new.xml b/core/res/res/layout/input_method_switch_item_new.xml
index 09ed650..10d938c 100644
--- a/core/res/res/layout/input_method_switch_item_new.xml
+++ b/core/res/res/layout/input_method_switch_item_new.xml
@@ -70,6 +70,7 @@
android:ellipsize="marquee"
android:singleLine="true"
android:fontFamily="google-sans-text"
+ android:textColor="@color/input_method_switch_on_item"
android:textAppearance="?attr/textAppearanceListItem"/>
</LinearLayout>
@@ -81,7 +82,7 @@
android:gravity="center_vertical"
android:layout_marginStart="12dp"
android:src="@drawable/ic_check_24dp"
- android:tint="?attr/materialColorOnSurface"
+ android:tint="@color/input_method_switch_on_item"
android:visibility="gone"
android:importantForAccessibility="no"/>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index de7477e..b6468ee 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3080,6 +3080,11 @@
<!-- Whether UI for multi user should be shown -->
<bool name="config_enableMultiUserUI">false</bool>
+ <!-- Whether to boot system with the headless system user, i.e. user 0. If set to true,
+ system will be booted with the headless system user, or user 0. It has no effect if device
+ is not in Headless System User Mode (HSUM). -->
+ <bool name="config_bootToHeadlessSystemUser">false</bool>
+
<!-- Whether multiple admins are allowed on the device. If set to true, new users can be created
with admin privileges and admin privileges can be granted/revoked from existing users. -->
<bool name="config_enableMultipleAdmins">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0d16e9c..9a52bd4 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -363,6 +363,7 @@
<java-symbol type="bool" name="config_canSwitchToHeadlessSystemUser"/>
<java-symbol type="bool" name="config_enableMultiUserUI"/>
<java-symbol type="bool" name="config_enableMultipleAdmins"/>
+ <java-symbol type="bool" name="config_bootToHeadlessSystemUser"/>
<java-symbol type="bool" name="config_omnipresentCommunalUser"/>
<java-symbol type="bool" name="config_enableNewAutoSelectNetworkUI"/>
<java-symbol type="bool" name="config_disableUsbPermissionDialogs"/>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
index 434885f..47d5274 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
@@ -22,10 +22,14 @@
import com.android.window.flags.Flags
/*
- * A shared class to check desktop mode flags state.
+ * An enum to check desktop mode flags state.
*
- * The class computes whether a Desktop Windowing flag should be enabled by using the aconfig flag
- * value and the developer option override state (if applicable).
+ * This enum provides a centralized way to control the behavior of flags related to desktop
+ * windowing features which are aiming for developer preview before their release. It allows
+ * developer option to override the default behavior of these flags.
+ *
+ * NOTE: Flags should only be added to this enum when they have received Product and UX
+ * alignment that the feature is ready for developer preview, otherwise just do a flag check.
*/
enum class DesktopModeFlags(
// Function called to obtain aconfig flag value.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 8ce7837..17869e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -19,8 +19,6 @@
import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
-import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.TaskInfo;
@@ -40,6 +38,7 @@
import com.android.wm.shell.compatui.api.CompatUIEvent;
import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonAppeared;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import java.util.function.Consumer;
@@ -83,7 +82,7 @@
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mCallback = callback;
mHasSizeCompat = taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat();
- if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
+ if (DesktopModeStatus.canEnterDesktopMode(context)
&& DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(context)) {
// Don't show the SCM button for freeform tasks
mHasSizeCompat &= !taskInfo.isFreeform();
@@ -139,7 +138,7 @@
boolean canShow) {
final boolean prevHasSizeCompat = mHasSizeCompat;
mHasSizeCompat = taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat();
- if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
+ if (DesktopModeStatus.canEnterDesktopMode(mContext)
&& DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)) {
// Don't show the SCM button for freeform tasks
mHasSizeCompat &= !taskInfo.isFreeform();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt
new file mode 100644
index 0000000..a489c4f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 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.dagger
+
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import dagger.Module
+import dagger.Provides
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.asCoroutineDispatcher
+
+/** Providers for various WmShell-specific coroutines-related constructs. */
+@Module
+class WMShellCoroutinesModule {
+ @Provides
+ @ShellMainThread
+ fun provideMainDispatcher(@ShellMainThread mainExecutor: ShellExecutor): CoroutineDispatcher =
+ mainExecutor.asCoroutineDispatcher()
+
+ @Provides
+ @ShellBackgroundThread
+ fun provideBackgroundDispatcher(
+ @ShellBackgroundThread backgroundExecutor: ShellExecutor
+ ): CoroutineDispatcher = backgroundExecutor.asCoroutineDispatcher()
+
+ @Provides
+ @WMSingleton
+ @ShellMainThread
+ fun provideApplicationScope(
+ @ShellMainThread applicationDispatcher: CoroutineDispatcher,
+ ): CoroutineScope = CoroutineScope(applicationDispatcher)
+
+ @Provides
+ @WMSingleton
+ @ShellBackgroundThread
+ fun provideBackgroundCoroutineScope(
+ @ShellBackgroundThread backgroundDispatcher: CoroutineDispatcher,
+ ): CoroutineScope = CoroutineScope(backgroundDispatcher)
+
+ @Provides
+ @WMSingleton
+ @ShellBackgroundThread
+ fun provideBackgroundCoroutineContext(
+ @ShellBackgroundThread backgroundDispatcher: CoroutineDispatcher
+ ): CoroutineContext = backgroundDispatcher + SupervisorJob()
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index b6f2a25..02cbe01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -132,7 +132,8 @@
sessionId,
taskUpdate.minimizeReason?.reason ?: UNSET_MINIMIZE_REASON,
taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON,
-
+ /* visible_task_count */
+ taskUpdate.visibleTaskCount
)
}
@@ -159,6 +160,7 @@
val taskY: Int,
val minimizeReason: MinimizeReason? = null,
val unminimizeReason: UnminimizeReason? = null,
+ val visibleTaskCount: Int,
)
// Default value used when the task was not minimized.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index 2e36ffe..336e5e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -295,7 +295,8 @@
postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
) {
postTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
- val currentTaskUpdate = buildTaskUpdateForTask(taskInfo)
+ val currentTaskUpdate = buildTaskUpdateForTask(taskInfo,
+ postTransitionVisibleFreeformTasks.size())
val previousTaskInfo = preTransitionVisibleFreeformTasks[taskId]
when {
// new tasks added
@@ -309,15 +310,17 @@
}
// old tasks that were resized or repositioned
// TODO(b/347935387): Log changes only once they are stable.
- buildTaskUpdateForTask(previousTaskInfo) != currentTaskUpdate ->
- desktopModeEventLogger.logTaskInfoChanged(sessionId, currentTaskUpdate)
+ buildTaskUpdateForTask(previousTaskInfo, postTransitionVisibleFreeformTasks.size())
+ != currentTaskUpdate ->
+ desktopModeEventLogger.logTaskInfoChanged(sessionId, currentTaskUpdate)
}
}
// find old tasks that were removed
preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) {
- desktopModeEventLogger.logTaskRemoved(sessionId, buildTaskUpdateForTask(taskInfo))
+ desktopModeEventLogger.logTaskRemoved(sessionId,
+ buildTaskUpdateForTask(taskInfo, postTransitionVisibleFreeformTasks.size()))
Trace.setCounter(
Trace.TRACE_TAG_WINDOW_MANAGER,
VISIBLE_TASKS_COUNTER_NAME,
@@ -327,7 +330,7 @@
}
}
- private fun buildTaskUpdateForTask(taskInfo: TaskInfo): TaskUpdate {
+ private fun buildTaskUpdateForTask(taskInfo: TaskInfo, visibleTasks: Int): TaskUpdate {
val screenBounds = taskInfo.configuration.windowConfiguration.bounds
val positionInParent = taskInfo.positionInParent
return TaskUpdate(
@@ -337,6 +340,7 @@
taskWidth = screenBounds.width(),
taskX = positionInParent.x,
taskY = positionInParent.y,
+ visibleTaskCount = visibleTasks,
)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index c97066a..ffd534b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -53,6 +53,7 @@
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.internal.protolog.ProtoLog
@@ -322,7 +323,7 @@
logW("moveBackgroundTaskToDesktop taskId=%d not found", taskId)
return false
}
- logV("moveBackgroundTaskToDesktop with taskId=%d, displayId=%d", taskId)
+ logV("moveBackgroundTaskToDesktop with taskId=%d", taskId)
// TODO(342378842): Instead of using default display, support multiple displays
val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
DEFAULT_DISPLAY, wct, taskId)
@@ -693,6 +694,10 @@
) {
releaseVisualIndicator()
if (!taskInfo.isResizeable && DesktopModeFlags.DISABLE_SNAP_RESIZE.isEnabled(context)) {
+ interactionJankMonitor.begin(
+ taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_non_resizable"
+ )
+
// reposition non-resizable app back to its original position before being dragged
returnToDragStartAnimator.start(
taskInfo.taskId,
@@ -701,6 +706,9 @@
endBounds = dragStartBounds
)
} else {
+ interactionJankMonitor.begin(
+ taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable"
+ )
snapToHalfScreen(taskInfo, currentDragBounds, position)
}
}
@@ -1104,12 +1112,11 @@
addMoveToDesktopChanges(wct, task)
// In some launches home task is moved behind new task being launched. Make sure
// that's not the case for launches in desktop.
- moveHomeTask(wct, toTop = false)
- // Move existing minimized tasks behind Home
- taskRepository.getFreeformTasksInZOrder(task.displayId)
- .filter { taskId -> taskRepository.isMinimizedTask(taskId) }
- .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) }
- .forEach { taskInfo -> wct.reorder(taskInfo.token, /* onTop= */ false) }
+ if (task.baseIntent.flags.and(Intent.FLAG_ACTIVITY_TASK_ON_HOME) != 0) {
+ bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
+ wct.reorder(task.token, true)
+ }
+
// Desktop Mode is already showing and we're launching a new Task - we might need to
// minimize another Task.
val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
index 24a7d77..4c5258f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
@@ -24,6 +24,7 @@
import android.view.SurfaceControl
import android.widget.Toast
import androidx.core.animation.addListener
+import com.android.internal.jank.Cuj
import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.R
import com.android.wm.shell.windowdecor.OnTaskRepositionAnimationListener
@@ -85,7 +86,7 @@
R.string.desktop_mode_non_resizable_snap_text,
Toast.LENGTH_SHORT
).show()
- // TODO(b/339582583) - add Jank CUJ using interactionJankMonitor
+ interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE)
}
)
addUpdateListener { anim ->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index bf185a4..96719fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -117,8 +117,8 @@
finishCallback.onTransitionFinished(null)
initialBounds = null
boundsAnimator = null
- interactionJankMonitor.end(
- Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
+ interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
+ interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE)
}
)
addUpdateListener { anim ->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 7ba6ec4..b102e40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -41,6 +41,7 @@
import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_CLEANUP_PIP_EXIT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
@@ -122,6 +123,8 @@
@Nullable
private IBinder mMoveToBackTransition;
private IBinder mRequestedEnterTransition;
+ private IBinder mCleanupTransition;
+
private WindowContainerToken mRequestedEnterTask;
/** The Task window that is currently in PIP windowing mode. */
@Nullable
@@ -232,10 +235,12 @@
// Exiting PIP.
final int type = info.getType();
- if (transition.equals(mExitTransition) || transition.equals(mMoveToBackTransition)) {
+ if (transition.equals(mExitTransition) || transition.equals(mMoveToBackTransition)
+ || transition.equals(mCleanupTransition)) {
mExitDestinationBounds.setEmpty();
mExitTransition = null;
mMoveToBackTransition = null;
+ mCleanupTransition = null;
mHasFadeOut = false;
if (mFinishCallback != null) {
callFinishCallback(null /* wct */);
@@ -269,6 +274,9 @@
removePipImmediately(info, startTransaction, finishTransaction, finishCallback,
pipTaskInfo);
break;
+ case TRANSIT_CLEANUP_PIP_EXIT:
+ cleanupPipExitTransition(startTransaction, finishCallback);
+ break;
default:
throw new IllegalStateException("mExitTransition with unexpected transit type="
+ transitTypeToString(type));
@@ -768,7 +776,19 @@
mPipAnimationController.resetAnimatorState();
finishTransaction.remove(pipLeash);
}
- finishCallback.onTransitionFinished(wct);
+
+ if (mFixedRotationState == FIXED_ROTATION_TRANSITION) {
+ // TODO(b/358226697): start a new transition with the WCT instead of applying it in
+ // the {@link finishCallback}, to ensure shell creates a transition for it.
+ finishCallback.onTransitionFinished(wct);
+ } else {
+ // Apply wct in separate transition so that it can be correctly handled by the
+ // {@link FreeformTaskTransitionObserver} when desktop windowing (which does not
+ // utilize fixed rotation transitions for exiting pip) is enabled (See b/288910069).
+ mCleanupTransition = mTransitions.startTransition(
+ TRANSIT_CLEANUP_PIP_EXIT, wct, this);
+ finishCallback.onTransitionFinished(null);
+ }
};
mFinishTransaction = finishTransaction;
@@ -914,6 +934,16 @@
finishCallback.onTransitionFinished(null);
}
+ /**
+ * For {@link Transitions#TRANSIT_CLEANUP_PIP_EXIT} which applies final config changes needed
+ * after the exit from pip transition animation finishes.
+ */
+ private void cleanupPipExitTransition(@NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ }
+
/** Whether we should handle the given {@link TransitionInfo} animation as entering PIP. */
private boolean isEnteringPip(@NonNull TransitionInfo info) {
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index f0d3668..7dc336b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -193,6 +193,9 @@
/** Remote Transition that split accepts but ultimately needs to be animated by the remote. */
public static final int TRANSIT_SPLIT_PASSTHROUGH = TRANSIT_FIRST_CUSTOM + 18;
+ /** Transition to set windowing mode after exit pip transition is finished animating. */
+ public static final int TRANSIT_CLEANUP_PIP_EXIT = WindowManager.TRANSIT_FIRST_CUSTOM + 19;
+
/** Transition type for desktop mode transitions. */
public static final int TRANSIT_DESKTOP_MODE_TYPES =
WindowManager.TRANSIT_FIRST_CUSTOM + 100;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index b1cb834..20a406f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -480,6 +480,8 @@
Toast.makeText(mContext,
R.string.desktop_mode_non_resizable_snap_text, Toast.LENGTH_SHORT).show();
} else {
+ mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext,
+ Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE, "maximize_menu_resizable");
mDesktopTasksController.snapToHalfScreen(decoration.mTaskInfo,
decoration.mTaskInfo.configuration.windowConfiguration.getBounds(),
left ? SnapPosition.LEFT : SnapPosition.RIGHT);
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
index e3660fe..b812c59 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
@@ -25,6 +25,7 @@
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.window.flags.Flags
import com.android.wm.shell.Utils
@@ -38,15 +39,20 @@
@RunWith(BlockJUnit4ClassRunner::class)
@Postsubmit
open class MaximizeAppWindow
-{
+@JvmOverloads
+constructor(rotation: Rotation = Rotation.ROTATION_0, isResizable: Boolean = true) {
+
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val tapl = LauncherInstrumentation()
private val wmHelper = WindowManagerStateHelper(instrumentation)
private val device = UiDevice.getInstance(instrumentation)
- private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val testApp = if (isResizable) {
+ DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ } else {
+ DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+ }
- @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL,
- Rotation.ROTATION_0)
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
@Before
fun setup() {
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
index 63e7387..03d970f 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
@@ -25,6 +25,7 @@
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.window.flags.Flags
import com.android.wm.shell.Utils
@@ -40,15 +41,24 @@
@Postsubmit
open class ResizeAppWithCornerResize
@JvmOverloads
-constructor(val rotation: Rotation = Rotation.ROTATION_0,
+constructor(
+ val rotation: Rotation = Rotation.ROTATION_0,
val horizontalChange: Int = 50,
- val verticalChange: Int = -50) {
+ val verticalChange: Int = -50,
+ val appProperty: AppProperty = AppProperty.STANDARD
+) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val tapl = LauncherInstrumentation()
private val wmHelper = WindowManagerStateHelper(instrumentation)
private val device = UiDevice.getInstance(instrumentation)
- private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val testApp =
+ DesktopModeAppHelper(
+ when (appProperty) {
+ AppProperty.STANDARD -> SimpleAppHelper(instrumentation)
+ AppProperty.NON_RESIZABLE -> NonResizeableAppHelper(instrumentation)
+ }
+ )
@Rule
@JvmField
@@ -64,15 +74,24 @@
@Test
open fun resizeAppWithCornerResize() {
- testApp.cornerResize(wmHelper,
+ testApp.cornerResize(
+ wmHelper,
device,
DesktopModeAppHelper.Corners.RIGHT_TOP,
horizontalChange,
- verticalChange)
+ verticalChange
+ )
}
@After
fun teardown() {
testApp.exit(wmHelper)
}
+
+ companion object {
+ enum class AppProperty {
+ STANDARD,
+ NON_RESIZABLE
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
new file mode 100644
index 0000000..685a3ba
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 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.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class SnapResizeAppWindowWithButton
+@JvmOverloads
+constructor(private val toLeft: Boolean = true, private val isResizable: Boolean = true) {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = if (isResizable) {
+ DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ } else {
+ DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+ }
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun snapResizeAppWindowWithButton() {
+ testApp.snapResizeDesktopApp(wmHelper, device, instrumentation.context, toLeft)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt
new file mode 100644
index 0000000..8a4aa63
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 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.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class SnapResizeAppWindowWithDrag
+@JvmOverloads
+constructor(private val toLeft: Boolean = true, private val isResizable: Boolean = true) {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = if (isResizable) {
+ DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ } else {
+ DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+ }
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun snapResizeAppWindowWithDrag() {
+ testApp.dragToSnapResizeRegion(wmHelper, device, toLeft)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index 70b3661..ca97229 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -104,7 +104,9 @@
/* session_id */
eq(SESSION_ID),
eq(UNSET_MINIMIZE_REASON),
- eq(UNSET_UNMINIMIZE_REASON))
+ eq(UNSET_UNMINIMIZE_REASON),
+ /* visible_task_count */
+ eq(TASK_COUNT))
}
}
@@ -131,7 +133,9 @@
/* session_id */
eq(SESSION_ID),
eq(UNSET_MINIMIZE_REASON),
- eq(UNSET_UNMINIMIZE_REASON))
+ eq(UNSET_UNMINIMIZE_REASON),
+ /* visible_task_count */
+ eq(TASK_COUNT))
}
}
@@ -159,7 +163,9 @@
/* session_id */
eq(SESSION_ID),
eq(UNSET_MINIMIZE_REASON),
- eq(UNSET_UNMINIMIZE_REASON))
+ eq(UNSET_UNMINIMIZE_REASON),
+ /* visible_task_count */
+ eq(TASK_COUNT))
}
}
@@ -190,7 +196,9 @@
/* minimize_reason */
eq(MinimizeReason.TASK_LIMIT.reason),
/* unminimize_reason */
- eq(UNSET_UNMINIMIZE_REASON))
+ eq(UNSET_UNMINIMIZE_REASON),
+ /* visible_task_count */
+ eq(TASK_COUNT))
}
}
@@ -221,7 +229,9 @@
/* minimize_reason */
eq(UNSET_MINIMIZE_REASON),
/* unminimize_reason */
- eq(UnminimizeReason.TASKBAR_TAP.reason))
+ eq(UnminimizeReason.TASKBAR_TAP.reason),
+ /* visible_task_count */
+ eq(TASK_COUNT))
}
}
@@ -233,15 +243,17 @@
private const val TASK_Y = 0
private const val TASK_HEIGHT = 100
private const val TASK_WIDTH = 100
+ private const val TASK_COUNT = 1
private val TASK_UPDATE = TaskUpdate(
- TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y
+ TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y,
+ visibleTaskCount = TASK_COUNT,
)
private fun createTaskUpdate(
minimizeReason: MinimizeReason? = null,
unminimizeReason: UnminimizeReason? = null,
) = TaskUpdate(TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, minimizeReason,
- unminimizeReason)
+ unminimizeReason, TASK_COUNT)
}
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index 74fc7b0..e49eb36 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -138,7 +138,8 @@
callOnTransitionReady(transitionInfo)
- verifyTaskAddedAndEnterLogging(EnterReason.APP_FREEFORM_INTENT, DEFAULT_TASK_UPDATE)
+ verifyTaskAddedAndEnterLogging(EnterReason.APP_FREEFORM_INTENT,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
}
@Test
@@ -152,7 +153,8 @@
callOnTransitionReady(transitionInfo)
- verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG, DEFAULT_TASK_UPDATE)
+ verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
}
@Test
@@ -165,7 +167,8 @@
callOnTransitionReady(transitionInfo)
- verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_MENU_BUTTON, DEFAULT_TASK_UPDATE)
+ verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_MENU_BUTTON,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
}
@Test
@@ -178,7 +181,8 @@
callOnTransitionReady(transitionInfo)
- verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW, DEFAULT_TASK_UPDATE)
+ verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
}
@Test
@@ -191,7 +195,8 @@
callOnTransitionReady(transitionInfo)
- verifyTaskAddedAndEnterLogging(EnterReason.KEYBOARD_SHORTCUT_ENTER, DEFAULT_TASK_UPDATE)
+ verifyTaskAddedAndEnterLogging(EnterReason.KEYBOARD_SHORTCUT_ENTER,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
}
@Test
@@ -201,7 +206,8 @@
callOnTransitionReady(transitionInfo)
- verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
+ verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
}
@Test
@@ -231,7 +237,8 @@
callOnTransitionReady(transitionInfo)
- verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
+ verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
}
@Test
@@ -261,7 +268,8 @@
callOnTransitionReady(transitionInfo)
- verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
+ verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
}
@Test
@@ -291,7 +299,8 @@
callOnTransitionReady(transitionInfo)
- verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
+ verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
}
@Test
@@ -324,7 +333,8 @@
callOnTransitionReady(transitionInfo)
- verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW, DEFAULT_TASK_UPDATE)
+ verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
}
@Test
@@ -335,7 +345,8 @@
callOnTransitionReady(transitionInfo)
- verifyTaskAddedAndEnterLogging(EnterReason.UNKNOWN_ENTER, DEFAULT_TASK_UPDATE)
+ verifyTaskAddedAndEnterLogging(EnterReason.UNKNOWN_ENTER,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
}
@Test
@@ -345,7 +356,8 @@
callOnTransitionReady(transitionInfo)
- verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON, DEFAULT_TASK_UPDATE)
+ verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
}
@Test
@@ -364,7 +376,8 @@
callOnTransitionReady(transitionInfo)
- verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON, DEFAULT_TASK_UPDATE)
+ verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
}
@Test
@@ -386,7 +399,8 @@
.build()
callOnTransitionReady(transitionInfo)
- verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG, DEFAULT_TASK_UPDATE)
+ verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
}
@Test
@@ -522,7 +536,8 @@
val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build()
callOnTransitionReady(transitionInfo2)
- verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
+ verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
}
@Test
@@ -538,7 +553,8 @@
callOnTransitionReady(transitionInfo)
verify(desktopModeEventLogger, times(1))
- .logTaskAdded(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2)))
+ .logTaskAdded(eq(sessionId),
+ eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2, visibleTaskCount = 2)))
verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
}
@@ -560,7 +576,8 @@
verify(desktopModeEventLogger, times(1))
.logTaskInfoChanged(
- eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100)))
+ eq(sessionId),
+ eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 1)))
verifyZeroInteractions(desktopModeEventLogger)
}
@@ -589,7 +606,8 @@
eq(sessionId),
eq(
DEFAULT_TASK_UPDATE.copy(
- taskWidth = DEFAULT_TASK_WIDTH + 100, taskHeight = DEFAULT_TASK_HEIGHT - 100)))
+ taskWidth = DEFAULT_TASK_WIDTH + 100, taskHeight = DEFAULT_TASK_HEIGHT - 100,
+ visibleTaskCount = 1)))
verifyZeroInteractions(desktopModeEventLogger)
}
@@ -613,7 +631,8 @@
verify(desktopModeEventLogger, times(1))
.logTaskInfoChanged(
- eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100)))
+ eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(
+ taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 2)))
verifyZeroInteractions(desktopModeEventLogger)
// task 2 resize
@@ -637,7 +656,9 @@
DEFAULT_TASK_UPDATE.copy(
instanceId = 2,
taskWidth = DEFAULT_TASK_WIDTH + 100,
- taskHeight = DEFAULT_TASK_HEIGHT - 100)))
+ taskHeight = DEFAULT_TASK_HEIGHT - 100,
+ visibleTaskCount = 2)),
+ )
verifyZeroInteractions(desktopModeEventLogger)
}
@@ -655,7 +676,8 @@
callOnTransitionReady(transitionInfo)
verify(desktopModeEventLogger, times(1))
- .logTaskRemoved(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2)))
+ .logTaskRemoved(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(
+ instanceId = 2, visibleTaskCount = 1)))
verify(desktopModeEventLogger, never()).logSessionExit(any(), any())
}
@@ -694,6 +716,7 @@
const val DEFAULT_TASK_WIDTH = 200
const val DEFAULT_TASK_X = 30
const val DEFAULT_TASK_Y = 70
+ const val DEFAULT_VISIBLE_TASK_COUNT = 0
val DEFAULT_TASK_UPDATE =
TaskUpdate(
DEFAULT_TASK_ID,
@@ -702,6 +725,7 @@
DEFAULT_TASK_WIDTH,
DEFAULT_TASK_X,
DEFAULT_TASK_Y,
+ visibleTaskCount = DEFAULT_VISIBLE_TASK_COUNT,
)
fun createTaskInfo(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 2e0af273..058a26a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -1515,8 +1515,34 @@
assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
- assertThat(wct.hierarchyOps).hasSize(2)
- wct.assertReorderAt(1, homeTask, toTop = false)
+ assertThat(wct.hierarchyOps).hasSize(1)
+ }
+
+ @Test
+ fun handleRequest_fullscreenTaskWithTaskOnHome_freeformVisible_returnSwitchToFreeformWCT() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val homeTask = setUpHomeTask()
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+
+ // There are 5 hops that are happening in this case:
+ // 1. Moving the fullscreen task to top as we add moveToDesktop() changes
+ // 2. Bringing home task to front
+ // 3. Pending intent for the wallpaper
+ // 4. Bringing the existing freeform task to top
+ // 5. Bringing the fullscreen task back at the top
+ assertThat(wct.hierarchyOps).hasSize(5)
+ wct.assertReorderAt(1, homeTask, toTop = true)
+ wct.assertReorderAt(4, fullscreenTask, toTop = true)
}
@Test
@@ -1541,19 +1567,34 @@
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
freeformTasks.forEach { markTaskVisible(it) }
val fullscreenTask = createFullscreenTask()
- val homeTask = setUpHomeTask(DEFAULT_DISPLAY)
val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
// Make sure we reorder the new task to top, and the back task to the bottom
- assertThat(wct!!.hierarchyOps.size).isEqualTo(3)
+ assertThat(wct!!.hierarchyOps.size).isEqualTo(2)
wct.assertReorderAt(0, fullscreenTask, toTop = true)
- wct.assertReorderAt(1, homeTask, toTop = false)
- wct.assertReorderAt(2, freeformTasks[0], toTop = false)
+ wct.assertReorderAt(1, freeformTasks[0], toTop = false)
}
@Test
- fun handleRequest_fullscreenTaskToFreeform_alreadyBeyondLimit_existingAndNewTasksAreMinimized() {
+ fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_otherTaskIsMinimized() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ freeformTasks.forEach { markTaskVisible(it) }
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ // Make sure we reorder the new task to top, and the back task to the bottom
+ assertThat(wct!!.hierarchyOps.size).isEqualTo(9)
+ wct.assertReorderAt(0, fullscreenTask, toTop = true)
+ wct.assertReorderAt(8, freeformTasks[0], toTop = false)
+ }
+
+ @Test
+ fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_existingAndNewTasksAreMinimized() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
val minimizedTask = setUpFreeformTask()
@@ -1562,15 +1603,16 @@
freeformTasks.forEach { markTaskVisible(it) }
val homeTask = setUpHomeTask()
val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
- assertThat(wct!!.hierarchyOps.size).isEqualTo(4)
+ assertThat(wct!!.hierarchyOps.size).isEqualTo(10)
wct.assertReorderAt(0, fullscreenTask, toTop = true)
- // Make sure we reorder the home task to the bottom, and minimized tasks below the home task.
- wct.assertReorderAt(1, homeTask, toTop = false)
- wct.assertReorderAt(2, minimizedTask, toTop = false)
- wct.assertReorderAt(3, freeformTasks[0], toTop = false)
+ // Make sure we reorder the home task to the top, desktop tasks to top of them and minimized
+ // task is under the home task.
+ wct.assertReorderAt(1, homeTask, toTop = true)
+ wct.assertReorderAt(9, freeformTasks[0], toTop = false)
}
@Test
diff --git a/libs/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h
index 31c9db7..3a3bfb47 100644
--- a/libs/hwui/ColorFilter.h
+++ b/libs/hwui/ColorFilter.h
@@ -106,7 +106,7 @@
private:
sk_sp<SkColorFilter> createInstance() override {
- return SkColorFilters::Matrix(mMatrix.data());
+ return SkColorFilters::Matrix(mMatrix.data(), SkColorFilters::Clamp::kNo);
}
private:
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index 6117330..7974a37 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -19,6 +19,7 @@
import static android.companion.CompanionDeviceManager.RESULT_CANCELED;
import static android.companion.CompanionDeviceManager.RESULT_DISCOVERY_TIMEOUT;
import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
+import static android.companion.CompanionDeviceManager.RESULT_SECURITY_ERROR;
import static android.companion.CompanionDeviceManager.RESULT_USER_REJECTED;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -33,6 +34,7 @@
import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_TITLES;
import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_PROFILES;
import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_SELF_MANAGED_PROFILES;
+import static com.android.companiondevicemanager.Utils.RESULT_CODE_TO_REASON;
import static com.android.companiondevicemanager.Utils.getApplicationLabel;
import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
import static com.android.companiondevicemanager.Utils.getIcon;
@@ -51,6 +53,7 @@
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
+import android.companion.Flags;
import android.companion.IAssociationRequestCallback;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -231,7 +234,7 @@
boolean forCancelDialog = intent.getBooleanExtra(EXTRA_FORCE_CANCEL_CONFIRMATION, false);
if (forCancelDialog) {
Slog.i(TAG, "Cancelling the user confirmation");
- cancel(RESULT_CANCELED);
+ cancel(RESULT_CANCELED, null);
return;
}
@@ -243,10 +246,15 @@
if (appCallback == null) {
return;
}
- Slog.e(TAG, "More than one AssociationRequests are processing.");
try {
- appCallback.onFailure(RESULT_INTERNAL_ERROR);
+ if (Flags.associationFailureCode()) {
+ appCallback.onFailure(
+ RESULT_SECURITY_ERROR, "More than one AssociationRequests are processing.");
+ } else {
+ appCallback.onFailure(
+ RESULT_INTERNAL_ERROR, "More than one AssociationRequests are processing.");
+ }
} catch (RemoteException ignore) {
}
}
@@ -257,7 +265,7 @@
// TODO: handle config changes without cancelling.
if (!isDone()) {
- cancel(RESULT_CANCELED); // will finish()
+ cancel(RESULT_CANCELED, null); // will finish()
}
}
@@ -331,7 +339,7 @@
&& CompanionDeviceDiscoveryService.getScanResult().getValue().isEmpty()) {
synchronized (LOCK) {
if (sDiscoveryStarted) {
- cancel(RESULT_DISCOVERY_TIMEOUT);
+ cancel(RESULT_DISCOVERY_TIMEOUT, null);
}
}
}
@@ -371,7 +379,7 @@
mCdmServiceReceiver.send(RESULT_CODE_ASSOCIATION_APPROVED, data);
}
- private void cancel(int failureCode) {
+ private void cancel(int errorCode, @Nullable CharSequence error) {
if (isDone()) {
Slog.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
return;
@@ -385,13 +393,14 @@
// First send callback to the app directly...
try {
- Slog.i(TAG, "Sending onFailure to app due to failureCode=" + failureCode);
- mAppCallback.onFailure(failureCode);
+ CharSequence errorMessage = error != null
+ ? error : RESULT_CODE_TO_REASON.get(errorCode);
+ mAppCallback.onFailure(errorCode, errorMessage);
} catch (RemoteException ignore) {
}
// ... then set result and finish ("sending" onActivityResult()).
- setResultAndFinish(null, failureCode);
+ setResultAndFinish(null, errorCode);
}
private void setResultAndFinish(@Nullable AssociationInfo association, int resultCode) {
@@ -436,7 +445,7 @@
}
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
- cancel(RESULT_INTERNAL_ERROR);
+ cancel(RESULT_INTERNAL_ERROR, e.getMessage());
return;
}
@@ -625,7 +634,7 @@
// Disable the button, to prevent more clicks.
v.setEnabled(false);
- cancel(RESULT_USER_REJECTED);
+ cancel(RESULT_USER_REJECTED, null);
}
private void onShowHelperDialog(View view) {
@@ -755,8 +764,8 @@
};
@Override
- public void onShowHelperDialogFailed() {
- cancel(RESULT_INTERNAL_ERROR);
+ public void onShowHelperDialogFailed(CharSequence errorMessage) {
+ cancel(RESULT_INTERNAL_ERROR, errorMessage);
}
@Override
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
index ec92987..b2d78da 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
@@ -54,7 +54,7 @@
private Button mButton;
interface CompanionVendorHelperDialogListener {
- void onShowHelperDialogFailed();
+ void onShowHelperDialogFailed(CharSequence error);
void onHelperDialogDismissed();
}
@@ -110,7 +110,7 @@
appLabel = getApplicationLabel(getContext(), packageName, userId);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
- mListener.onShowHelperDialogFailed();
+ mListener.onShowHelperDialogFailed(e.getMessage());
return;
}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
index 8c14f80..2f97132 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
@@ -16,6 +16,15 @@
package com.android.companiondevicemanager;
+import static android.companion.CompanionDeviceManager.REASON_CANCELED;
+import static android.companion.CompanionDeviceManager.REASON_DISCOVERY_TIMEOUT;
+import static android.companion.CompanionDeviceManager.REASON_USER_REJECTED;
+import static android.companion.CompanionDeviceManager.RESULT_CANCELED;
+import static android.companion.CompanionDeviceManager.RESULT_DISCOVERY_TIMEOUT;
+import static android.companion.CompanionDeviceManager.RESULT_USER_REJECTED;
+
+import static java.util.Collections.unmodifiableMap;
+
import android.annotation.NonNull;
import android.annotation.StringRes;
import android.content.Context;
@@ -31,6 +40,9 @@
import android.os.ResultReceiver;
import android.text.Html;
import android.text.Spanned;
+import android.util.ArrayMap;
+
+import java.util.Map;
/**
* Utilities.
@@ -40,6 +52,17 @@
"android.companion.vendor_icon";
private static final String COMPANION_DEVICE_ACTIVITY_VENDOR_NAME =
"android.companion.vendor_name";
+ // This map solely the common error messages that occur during the Association
+ // creation process.
+ static final Map<Integer, String> RESULT_CODE_TO_REASON;
+ static {
+ final Map<Integer, String> map = new ArrayMap<>();
+ map.put(RESULT_CANCELED, REASON_CANCELED);
+ map.put(RESULT_USER_REJECTED, REASON_USER_REJECTED);
+ map.put(RESULT_DISCOVERY_TIMEOUT, REASON_DISCOVERY_TIMEOUT);
+
+ RESULT_CODE_TO_REASON = unmodifiableMap(map);
+ }
/**
* Convert an instance of a "locally-defined" ResultReceiver to an instance of
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2e98c1f..f98b29a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -484,6 +484,7 @@
<activity android:name=".touchpad.tutorial.ui.view.TouchpadTutorialActivity"
android:exported="true"
+ android:showForAllUsers="true"
android:screenOrientation="userLandscape"
android:theme="@style/Theme.AppCompat.NoActionBar">
<intent-filter>
@@ -494,6 +495,7 @@
<activity android:name=".inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity"
android:exported="true"
+ android:showForAllUsers="true"
android:screenOrientation="userLandscape"
android:theme="@style/Theme.AppCompat.NoActionBar">
<intent-filter>
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 7c0db8d..0b364ac 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -541,6 +541,16 @@
}
flag {
+ name: "clipboard_shared_transitions"
+ namespace: "systemui"
+ description: "Show shared transitions from clipboard"
+ bug: "360843770"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "clipboard_image_timeout"
namespace: "systemui"
description: "Wait for clipboard image to load before showing UI"
@@ -1346,3 +1356,14 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "face_message_defer_update"
+ namespace: "systemui"
+ description: "Only analyze the last n frames when determining whether to defer a face auth help message like low light"
+ bug: "351863611"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 0bef05dc..9e292d0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -68,7 +68,6 @@
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
@@ -82,7 +81,6 @@
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.thenIf
-import com.android.internal.policy.SystemBarUtils
import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.res.R
@@ -137,14 +135,16 @@
.notificationHeadsUpHeight(stackScrollView)
.debugBackground(viewModel, DEBUG_HUN_COLOR)
.onGloballyPositioned { coordinates: LayoutCoordinates ->
+ val positionInWindow = coordinates.positionInWindow()
val boundsInWindow = coordinates.boundsInWindow()
debugLog(viewModel) {
"HUNS onGloballyPositioned:" +
" size=${coordinates.size}" +
" bounds=$boundsInWindow"
}
- // Note: boundsInWindow doesn't scroll off the screen
- stackScrollView.setHeadsUpTop(boundsInWindow.top)
+ // Note: boundsInWindow doesn't scroll off the screen, so use positionInWindow
+ // for top bound, which can scroll off screen while snoozing
+ stackScrollView.setHeadsUpTop(positionInWindow.y)
stackScrollView.setHeadsUpBottom(boundsInWindow.bottom)
}
)
@@ -159,16 +159,10 @@
stackScrollView: NotificationScrollView,
viewModel: NotificationsPlaceholderViewModel,
) {
- val context = LocalContext.current
- val density = LocalDensity.current
- val statusBarHeight = SystemBarUtils.getStatusBarHeight(context)
- val headsUpPadding =
- with(density) { dimensionResource(id = R.dimen.heads_up_status_bar_padding).roundToPx() }
-
val isHeadsUp by viewModel.isHeadsUpOrAnimatingAway.collectAsStateWithLifecycle(false)
var scrollOffset by remember { mutableFloatStateOf(0f) }
- val minScrollOffset = -(statusBarHeight + headsUpPadding.toFloat())
+ val minScrollOffset = -(stackScrollView.getHeadsUpInset().toFloat())
val maxScrollOffset = 0f
val scrollableState = rememberScrollableState { delta ->
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index f399436..0555346 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -103,10 +103,10 @@
when {
isSplitShade -> UnsquishingQS(squishiness)
fromScene == Scenes.Shade && toScene == Scenes.QuickSettings -> {
- Expanding(progress)
+ Expanding { progress }
}
fromScene == Scenes.QuickSettings && toScene == Scenes.Shade -> {
- Collapsing(progress)
+ Collapsing { progress }
}
fromScene == Scenes.Shade || toScene == Scenes.Shade -> {
UnsquishingQQS(squishiness)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index cfe0bec..15a4a40 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -17,6 +17,8 @@
package com.android.systemui.bouncer.domain.interactor
import android.content.pm.UserInfo
+import android.hardware.biometrics.BiometricFaceConstants
+import android.hardware.biometrics.BiometricSourceType
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -25,6 +27,7 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Pattern
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FaceSensorInfo
@@ -58,7 +61,9 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -77,6 +82,8 @@
@Mock private lateinit var securityModel: KeyguardSecurityModel
@Mock private lateinit var countDownTimerUtil: CountDownTimerUtil
@Mock private lateinit var systemPropertiesHelper: SystemPropertiesHelper
+ @Captor
+ private lateinit var keyguardUpdateMonitorCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
private lateinit var underTest: BouncerMessageInteractor
@@ -207,6 +214,52 @@
}
@Test
+ fun resetMessageBackToDefault_faceAuthRestarts() =
+ testScope.runTest {
+ init()
+ verify(updateMonitor).registerCallback(keyguardUpdateMonitorCaptor.capture())
+ val bouncerMessage by collectLastValue(underTest.bouncerMessage)
+
+ underTest.setFaceAcquisitionMessage("not empty")
+
+ assertThat(primaryResMessage(bouncerMessage))
+ .isEqualTo("Unlock with PIN or fingerprint")
+ assertThat(bouncerMessage?.secondaryMessage?.message).isEqualTo("not empty")
+
+ keyguardUpdateMonitorCaptor.value.onBiometricAcquired(
+ BiometricSourceType.FACE,
+ BiometricFaceConstants.FACE_ACQUIRED_START
+ )
+
+ assertThat(primaryResMessage(bouncerMessage))
+ .isEqualTo("Unlock with PIN or fingerprint")
+ assertThat(bouncerMessage?.secondaryMessage?.message).isNull()
+ }
+
+ @Test
+ fun faceRestartDoesNotResetFingerprintMessage() =
+ testScope.runTest {
+ init()
+ verify(updateMonitor).registerCallback(keyguardUpdateMonitorCaptor.capture())
+ val bouncerMessage by collectLastValue(underTest.bouncerMessage)
+
+ underTest.setFingerprintAcquisitionMessage("not empty")
+
+ assertThat(primaryResMessage(bouncerMessage))
+ .isEqualTo("Unlock with PIN or fingerprint")
+ assertThat(bouncerMessage?.secondaryMessage?.message).isEqualTo("not empty")
+
+ keyguardUpdateMonitorCaptor.value.onBiometricAcquired(
+ BiometricSourceType.FACE,
+ BiometricFaceConstants.FACE_ACQUIRED_START
+ )
+
+ assertThat(primaryResMessage(bouncerMessage))
+ .isEqualTo("Unlock with PIN or fingerprint")
+ assertThat(bouncerMessage?.secondaryMessage?.message).isEqualTo("not empty")
+ }
+
+ @Test
fun setFingerprintMessage_propagateValue() =
testScope.runTest {
init()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
index c5518b0..2ba4bf9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
@@ -36,6 +36,7 @@
import com.android.systemui.keyguard.data.repository.realKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -834,4 +835,86 @@
)
)
}
+
+ /**
+ * KTF: LOCKSCREEN -> ALTERNATE_BOUNCER starts but then STL: GLANCEABLE_HUB -> BLANK interrupts.
+ *
+ * Verifies that we correctly cancel the previous KTF state before starting the glanceable hub
+ * transition.
+ */
+ @Test
+ fun transition_to_blank_after_ktf_started_another_transition() =
+ testScope.runTest {
+ // Another transition has already started to the alternate bouncer.
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ from = LOCKSCREEN,
+ to = ALTERNATE_BOUNCER,
+ animator = null,
+ ownerName = "external",
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ ),
+ )
+
+ val allSteps by collectValues(keyguardTransitionRepository.transitions)
+ // Keep track of existing size to drop any pre-existing steps that we don't
+ // care about.
+ val numToDrop = allSteps.size
+
+ sceneTransitions.value = hubToBlank
+ runCurrent()
+ progress.emit(0.4f)
+ runCurrent()
+ // We land on blank.
+ sceneTransitions.value = Idle(CommunalScenes.Blank)
+
+ // We should cancel the previous ALTERNATE_BOUNCER transition and transition back
+ // to the GLANCEABLE_HUB before we can transition away from it.
+ assertThat(allSteps.drop(numToDrop))
+ .containsExactly(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = ALTERNATE_BOUNCER,
+ transitionState = CANCELED,
+ value = 0f,
+ ownerName = "external",
+ ),
+ TransitionStep(
+ from = ALTERNATE_BOUNCER,
+ to = GLANCEABLE_HUB,
+ transitionState = STARTED,
+ value = 1f,
+ ownerName = ownerName,
+ ),
+ TransitionStep(
+ from = ALTERNATE_BOUNCER,
+ to = GLANCEABLE_HUB,
+ transitionState = FINISHED,
+ value = 1f,
+ ownerName = ownerName,
+ ),
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = LOCKSCREEN,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = ownerName,
+ ),
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = LOCKSCREEN,
+ transitionState = RUNNING,
+ value = 0.4f,
+ ownerName = ownerName,
+ ),
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = LOCKSCREEN,
+ transitionState = FINISHED,
+ value = 1f,
+ ownerName = ownerName,
+ ),
+ )
+ .inOrder()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 1bc2e24..5a6f2be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1968,47 +1968,6 @@
@Test
@DisableSceneContainer
- @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun glanceableHubToLockscreen_communalKtfRefactor() =
- testScope.runTest {
- // GIVEN a prior transition has run to GLANCEABLE_HUB
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- runCurrent()
- clearInvocations(transitionRepository)
-
- // WHEN a transition away from glanceable hub starts
- val currentScene = CommunalScenes.Communal
- val targetScene = CommunalScenes.Blank
-
- val progress = MutableStateFlow(0f)
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Transition(
- fromScene = currentScene,
- toScene = targetScene,
- currentScene = flowOf(targetScene),
- progress = progress,
- isInitiatedByUserInput = false,
- isUserInputOngoing = flowOf(false),
- )
- )
- communalSceneInteractor.setTransitionState(transitionState)
- progress.value = .1f
- runCurrent()
-
- assertThat(transitionRepository)
- .startedTransition(
- ownerName = CommunalSceneTransitionInteractor::class.simpleName,
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.LOCKSCREEN,
- animatorAssertion = { it.isNull() }, // transition should be manually animated
- )
-
- coroutineContext.cancelChildren()
- }
-
- @Test
- @DisableSceneContainer
@DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun glanceableHubToDozing() =
testScope.runTest {
@@ -2260,54 +2219,6 @@
}
@Test
- @DisableSceneContainer
- @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun glanceableHubToDreaming_communalKtfRefactor() =
- testScope.runTest {
- // GIVEN that we are dreaming and not dozing
- powerInteractor.setAwakeForTest()
- keyguardRepository.setDreaming(true)
- keyguardRepository.setDreamingWithOverlay(true)
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
- )
- advanceTimeBy(600L)
-
- // GIVEN a prior transition has run to GLANCEABLE_HUB
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- runCurrent()
- clearInvocations(transitionRepository)
-
- // WHEN a transition away from glanceable hub starts
- val currentScene = CommunalScenes.Communal
- val targetScene = CommunalScenes.Blank
-
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Transition(
- fromScene = currentScene,
- toScene = targetScene,
- currentScene = flowOf(targetScene),
- progress = flowOf(0f, 0.1f),
- isInitiatedByUserInput = false,
- isUserInputOngoing = flowOf(false),
- )
- )
- communalSceneInteractor.setTransitionState(transitionState)
- runCurrent()
-
- assertThat(transitionRepository)
- .startedTransition(
- ownerName = CommunalSceneTransitionInteractor::class.simpleName,
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.DREAMING,
- animatorAssertion = { it.isNull() }, // transition should be manually animated
- )
-
- coroutineContext.cancelChildren()
- }
-
- @Test
@BrokenWithSceneContainer(339465026)
@EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun glanceableHubToOccludedDoesNotTriggerWhenDreamStateChanges_communalKtfRefactor() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 6dbe94b..a407468 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -360,6 +360,25 @@
}
@Test
+ fun alpha_transitionBetweenHubAndDream_isZero() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha(viewState))
+
+ // Default value check
+ assertThat(alpha).isEqualTo(1f)
+
+ // Start transitioning between DREAM and HUB but don't finish.
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ throughTransitionState = TransitionState.STARTED,
+ )
+
+ assertThat(alpha).isEqualTo(0f)
+ }
+
+ @Test
@EnableSceneContainer
fun alpha_transitionToHub_isZero_scene_container() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
index 94fa9b9..e0a53f8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
@@ -31,9 +31,8 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
-import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
-import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,7 +43,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -55,12 +53,7 @@
private val dispatcher = kosmos.testDispatcher
private val zenModeRepository = kosmos.fakeZenModeRepository
- private val underTest =
- ModesTileDataInteractor(
- context,
- ZenModeInteractor(zenModeRepository, mock<NotificationSettingsRepository>()),
- dispatcher
- )
+ private val underTest = ModesTileDataInteractor(context, kosmos.zenModeInteractor, dispatcher)
@Before
fun setUp() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index d472d98..22913f1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -255,7 +255,7 @@
runCurrent()
clearInvocations(qsImpl!!)
- underTest.setState(QSSceneAdapter.State.Expanding(progress))
+ underTest.setState(QSSceneAdapter.State.Expanding { progress })
with(qsImpl!!) {
verify(this).setQsVisible(true)
verify(this, never())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
index 63ce67c..41b5988 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
@@ -20,7 +20,12 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.ui.adapter.ExpandingSubject.Companion.assertThatExpanding
import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Companion.Collapsing
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth.assertAbout
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -32,33 +37,59 @@
@Test
fun expanding_squishiness1() {
- assertThat(QSSceneAdapter.State.Expanding(0.3f).squishiness()).isEqualTo(1f)
+ assertThat(QSSceneAdapter.State.Expanding { 0.3f }.squishiness()).isEqualTo(1f)
}
@Test
fun expandingSpecialValues() {
- assertThat(QSSceneAdapter.State.QQS).isEqualTo(QSSceneAdapter.State.Expanding(0f))
- assertThat(QSSceneAdapter.State.QS).isEqualTo(QSSceneAdapter.State.Expanding(1f))
+ assertThatExpanding(QSSceneAdapter.State.QQS)
+ .isEqualTo(QSSceneAdapter.State.Expanding { 0f })
+ assertThatExpanding(QSSceneAdapter.State.QS)
+ .isEqualTo(QSSceneAdapter.State.Expanding { 1f })
}
@Test
fun collapsing() {
val collapsingProgress = 0.3f
- assertThat(Collapsing(collapsingProgress))
- .isEqualTo(QSSceneAdapter.State.Expanding(1 - collapsingProgress))
+ assertThatExpanding(Collapsing { collapsingProgress })
+ .isEqualTo(QSSceneAdapter.State.Expanding { 1 - collapsingProgress })
}
@Test
fun unsquishingQQS_expansionSameAsQQS() {
val squishiness = 0.6f
- assertThat(QSSceneAdapter.State.UnsquishingQQS { squishiness }.expansion)
- .isEqualTo(QSSceneAdapter.State.QQS.expansion)
+ assertThat(QSSceneAdapter.State.UnsquishingQQS { squishiness }.expansion())
+ .isEqualTo(QSSceneAdapter.State.QQS.expansion())
}
@Test
fun unsquishingQS_expansionSameAsQS() {
val squishiness = 0.6f
- assertThat(QSSceneAdapter.State.UnsquishingQS { squishiness }.expansion)
- .isEqualTo(QSSceneAdapter.State.QS.expansion)
+ assertThat(QSSceneAdapter.State.UnsquishingQS { squishiness }.expansion())
+ .isEqualTo(QSSceneAdapter.State.QS.expansion())
+ }
+}
+
+private class ExpandingSubject(
+ metadata: FailureMetadata,
+ private val actual: QSSceneAdapter.State.Expanding?
+) : Subject(metadata, actual) {
+ fun isEqualTo(expected: QSSceneAdapter.State.Expanding) {
+ isNotNull()
+ check("expansion()")
+ .that(actual?.expansion?.invoke())
+ .isEqualTo(expected.expansion.invoke())
+ }
+
+ companion object {
+ fun expanding(): Factory<ExpandingSubject, QSSceneAdapter.State.Expanding> {
+ return Factory { metadata: FailureMetadata, actual: QSSceneAdapter.State.Expanding? ->
+ ExpandingSubject(metadata, actual)
+ }
+ }
+
+ fun assertThatExpanding(actual: QSSceneAdapter.State.Expanding): ExpandingSubject {
+ return assertAbout(expanding()).that(actual)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index c15a4e5..163b9b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -58,6 +58,7 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
import com.android.systemui.scene.domain.startable.sceneContainerStartable
+import com.android.systemui.scene.shared.logger.sceneLogger
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
@@ -133,6 +134,7 @@
sceneInteractor = sceneInteractor,
falsingInteractor = kosmos.falsingInteractor,
powerInteractor = kosmos.powerInteractor,
+ logger = kosmos.sceneLogger,
motionEventHandlerReceiver = {},
)
.apply { setTransitionState(transitionState) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 64a13de..8f8d2e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -1682,6 +1682,7 @@
underTest.start()
// run all pending dismiss succeeded/cancelled calls from setup:
+ runCurrent()
kosmos.fakeExecutor.runAllReady()
val dismissCallback: IKeyguardDismissCallback = mock()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index b315783..f856c55 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.logger.sceneLogger
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
@@ -72,6 +73,7 @@
sceneInteractor = sceneInteractor,
falsingInteractor = kosmos.falsingInteractor,
powerInteractor = kosmos.powerInteractor,
+ logger = kosmos.sceneLogger,
motionEventHandlerReceiver = { motionEventHandler ->
[email protected] = motionEventHandler
},
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
index eac86e5..ce9b3be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
@@ -23,7 +23,6 @@
import android.os.Handler
import android.testing.TestableLooper
import android.view.View
-import android.widget.FrameLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -46,6 +45,8 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.never
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -67,12 +68,9 @@
@Mock private lateinit var session: SmartspaceSession
- private lateinit var controller: CommunalSmartspaceController
+ private val preconditionListenerCaptor = argumentCaptor<SmartspacePrecondition.Listener>()
- // TODO(b/272811280): Remove usage of real view
- private val fakeParent by lazy {
- FrameLayout(context)
- }
+ private lateinit var controller: CommunalSmartspaceController
/**
* A class which implements SmartspaceView and extends View. This is mocked to provide the right
@@ -155,6 +153,26 @@
verify(session).close()
}
+ /** Ensures smartspace session begins when precondition is met if there is any listener. */
+ @Test
+ fun testConnectOnPreconditionMet() {
+ // Precondition not met
+ `when`(precondition.conditionsMet()).thenReturn(false)
+ controller.addListener(listener)
+
+ // Verify session not created because precondition not met
+ verify(smartspaceManager, never()).createSmartspaceSession(any())
+
+ // Precondition met
+ `when`(precondition.conditionsMet()).thenReturn(true)
+ verify(precondition).addListener(preconditionListenerCaptor.capture())
+ val preconditionListener = preconditionListenerCaptor.firstValue
+ preconditionListener.onCriteriaChanged()
+
+ // Verify session created
+ verify(smartspaceManager).createSmartspaceSession(any())
+ }
+
/**
* Ensures session is closed and weather plugin unregisters the notifier when weather smartspace
* view is detached.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 11504aa..20d3a7b6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy.domain.interactor
+import android.app.AutomaticZenRule
import android.app.NotificationManager.Policy
import android.provider.Settings
import android.provider.Settings.Secure.ZEN_DURATION
@@ -217,4 +218,35 @@
assertThat(zenModeRepository.getModeActiveDuration(manualDnd.id))
.isEqualTo(Duration.ofMinutes(60))
}
+
+ @Test
+ fun mainActiveMode_returnsMainActiveMode() =
+ testScope.runTest {
+ val mainActiveMode by collectLastValue(underTest.mainActiveMode)
+
+ zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME)
+ zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER)
+
+ runCurrent()
+ assertThat(mainActiveMode).isNull()
+
+ zenModeRepository.activateMode("Other")
+ runCurrent()
+ assertThat(mainActiveMode).isNotNull()
+ assertThat(mainActiveMode!!.id).isEqualTo("Other")
+
+ zenModeRepository.activateMode("Bedtime")
+ runCurrent()
+ assertThat(mainActiveMode).isNotNull()
+ assertThat(mainActiveMode!!.id).isEqualTo("Bedtime")
+
+ zenModeRepository.deactivateMode("Other")
+ runCurrent()
+ assertThat(mainActiveMode).isNotNull()
+ assertThat(mainActiveMode!!.id).isEqualTo("Bedtime")
+
+ zenModeRepository.deactivateMode("Bedtime")
+ runCurrent()
+ assertThat(mainActiveMode).isNull()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
index 094dc0a..e939f37 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.data.repository
+import android.hardware.biometrics.BiometricSourceType
import com.android.systemui.bouncer.shared.model.BouncerMessageModel
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
@@ -26,7 +27,12 @@
interface BouncerMessageRepository {
val bouncerMessage: Flow<BouncerMessageModel>
- fun setMessage(message: BouncerMessageModel)
+ fun setMessage(message: BouncerMessageModel, source: BiometricSourceType? = null)
+
+ /**
+ * Return the source of the current [bouncerMessage] if it was set from a biometric. Else, null.
+ */
+ fun getMessageSource(): BiometricSourceType?
}
@SysUISingleton
@@ -34,8 +40,14 @@
private val _bouncerMessage = MutableStateFlow(BouncerMessageModel())
override val bouncerMessage: Flow<BouncerMessageModel> = _bouncerMessage
+ private var messageSource: BiometricSourceType? = null
- override fun setMessage(message: BouncerMessageModel) {
+ override fun setMessage(message: BouncerMessageModel, source: BiometricSourceType?) {
_bouncerMessage.value = message
+ messageSource = source
+ }
+
+ override fun getMessageSource(): BiometricSourceType? {
+ return messageSource
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
index 8e12646..85fffbf 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.domain.interactor
+import android.hardware.biometrics.BiometricFaceConstants
import android.hardware.biometrics.BiometricSourceType
import android.os.CountDownTimer
import com.android.keyguard.KeyguardSecurityModel
@@ -115,7 +116,8 @@
isFingerprintAuthCurrentlyAllowedOnBouncer.value
)
.toMessage()
- }
+ },
+ biometricSourceType,
)
}
@@ -123,7 +125,12 @@
biometricSourceType: BiometricSourceType?,
acquireInfo: Int
) {
- super.onBiometricAcquired(biometricSourceType, acquireInfo)
+ if (
+ repository.getMessageSource() == BiometricSourceType.FACE &&
+ acquireInfo == BiometricFaceConstants.FACE_ACQUIRED_START
+ ) {
+ repository.setMessage(defaultMessage)
+ }
}
override fun onBiometricAuthenticated(
@@ -131,7 +138,7 @@
biometricSourceType: BiometricSourceType?,
isStrongBiometric: Boolean
) {
- repository.setMessage(defaultMessage)
+ repository.setMessage(defaultMessage, biometricSourceType)
}
}
@@ -291,7 +298,8 @@
currentSecurityMode,
value,
isFingerprintAuthCurrentlyAllowedOnBouncer.value
- )
+ ),
+ BiometricSourceType.FINGERPRINT,
)
}
@@ -302,7 +310,8 @@
currentSecurityMode,
value,
isFingerprintAuthCurrentlyAllowedOnBouncer.value
- )
+ ),
+ BiometricSourceType.FACE,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 040af90..aabfbd1 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -20,6 +20,7 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
import static com.android.systemui.Flags.clipboardImageTimeout;
+import static com.android.systemui.Flags.clipboardSharedTransitions;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
@@ -33,7 +34,6 @@
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
-import static com.android.systemui.flags.Flags.CLIPBOARD_SHARED_TRANSITIONS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -207,7 +207,7 @@
mClipboardUtils = clipboardUtils;
mBgExecutor = bgExecutor;
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
mView.setCallbacks(this);
} else {
mView.setCallbacks(mClipboardCallbacks);
@@ -220,7 +220,7 @@
});
mTimeoutHandler.setOnTimeoutRunnable(() -> {
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
finish(CLIPBOARD_OVERLAY_TIMED_OUT);
} else {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT);
@@ -232,7 +232,7 @@
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
finish(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
} else {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
@@ -248,7 +248,7 @@
@Override
public void onReceive(Context context, Intent intent) {
if (SCREENSHOT_ACTION.equals(intent.getAction())) {
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
finish(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
} else {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
@@ -481,7 +481,7 @@
remoteAction.ifPresent(action -> {
mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
mView.post(() -> mView.setActionChip(action, () -> {
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
finish(CLIPBOARD_OVERLAY_ACTION_TAPPED);
} else {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
@@ -528,7 +528,7 @@
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
if (!mView.isInTouchRegion(
(int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
finish(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
} else {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
@@ -575,6 +575,10 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
+ // check again after animation to see if we should still be minimized
+ if (mIsMinimized && !shouldShowMinimized(mWindow.getWindowInsets())) {
+ animateFromMinimized();
+ }
if (mOnUiUpdate != null) {
mOnUiUpdate.run();
}
@@ -690,14 +694,14 @@
@Override
public void onDismissButtonTapped() {
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
finish(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
}
}
@Override
public void onRemoteCopyButtonTapped() {
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
finish(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED,
IntentCreator.getRemoteCopyIntent(mClipboardModel.getClipData(), mContext));
}
@@ -705,7 +709,7 @@
@Override
public void onShareButtonTapped() {
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
if (mClipboardModel.getType() != ClipboardModel.Type.OTHER) {
finishWithSharedTransition(CLIPBOARD_OVERLAY_SHARE_TAPPED,
IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext));
@@ -715,7 +719,7 @@
@Override
public void onPreviewTapped() {
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
switch (mClipboardModel.getType()) {
case TEXT:
finish(CLIPBOARD_OVERLAY_EDIT_TAPPED,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
index 6343752..5bbb46d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
@@ -223,19 +223,15 @@
// KTF for this case and just collect the new progress instead.
collectProgress(transition)
} else if (transition.toScene == CommunalScenes.Communal) {
- if (currentTransitionId != null) {
- if (currentToState == KeyguardState.GLANCEABLE_HUB) {
- transitionKtfTo(transitionInteractor.getStartedFromState())
- }
+ if (currentToState == KeyguardState.GLANCEABLE_HUB) {
+ transitionKtfTo(transitionInteractor.getStartedFromState())
}
startTransitionToGlanceableHub()
collectProgress(transition)
} else if (transition.toScene == CommunalScenes.Blank) {
- if (currentTransitionId != null) {
- // Another transition started before this one is completed. Transition to the
- // GLANCEABLE_HUB state so that we can properly transition away from it.
- transitionKtfTo(KeyguardState.GLANCEABLE_HUB)
- }
+ // Another transition started before this one is completed. Transition to the
+ // GLANCEABLE_HUB state so that we can properly transition away from it.
+ transitionKtfTo(KeyguardState.GLANCEABLE_HUB)
startTransitionFromGlanceableHub()
collectProgress(transition)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
index 80db535..012c844 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
@@ -65,6 +65,12 @@
var preconditionListener =
object : SmartspacePrecondition.Listener {
override fun onCriteriaChanged() {
+ if (session == null && hasActiveSessionListeners()) {
+ Log.d(TAG, "Precondition criteria changed. Attempting to connect session.")
+ connectSession()
+ return
+ }
+
reloadSmartspace()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e5f3a57..4d75d66 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -337,10 +337,6 @@
// TODO(b/278714186) Tracking Bug
@JvmField
val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag("clipboard_image_timeout", teamfood = true)
- // TODO(b/279405451): Tracking Bug
- @JvmField
- val CLIPBOARD_SHARED_TRANSITIONS =
- unreleasedFlag("clipboard_shared_transitions", teamfood = true)
// 1900
@JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 06f77bf..54964d6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -33,6 +33,8 @@
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
@@ -45,6 +47,7 @@
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.kotlin.BooleanFlowOperators.any
import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.sample
@@ -198,29 +201,37 @@
.distinctUntilChanged()
/**
- * Keyguard should not show while the communal hub is fully visible. This check is added since
- * at the moment, closing the notification shade will cause the keyguard alpha to be set back to
- * 1. Also ensure keyguard is never visible when GONE.
+ * Keyguard states which should fully hide the keyguard.
+ *
+ * Note: [GONE] is not included as it is handled separately.
+ */
+ private val hiddenKeyguardStates = listOf(OCCLUDED, DREAMING, GLANCEABLE_HUB)
+
+ /**
+ * Keyguard should not show if fully transitioned into a hidden keyguard state or if
+ * transitioning between hidden states.
*/
private val hideKeyguard: Flow<Boolean> =
- combine(
- communalInteractor.isIdleOnCommunal,
+ (hiddenKeyguardStates.map { state ->
keyguardTransitionInteractor
- .transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
+ .transitionValue(state)
.map { it == 1f }
- .onStart { emit(false) },
- keyguardTransitionInteractor
- .transitionValue(OCCLUDED)
- .map { it == 1f }
- .onStart { emit(false) },
- keyguardTransitionInteractor
- .transitionValue(KeyguardState.DREAMING)
- .map { it == 1f }
- .onStart { emit(false) },
- ) { isIdleOnCommunal, isGone, isOccluded, isDreaming ->
- isIdleOnCommunal || isGone || isOccluded || isDreaming
- }
- .distinctUntilChanged()
+ .onStart { emit(false) }
+ } +
+ listOf(
+ communalInteractor.isIdleOnCommunal,
+ keyguardTransitionInteractor
+ .transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
+ .map { it == 1f }
+ .onStart { emit(false) },
+ keyguardTransitionInteractor
+ .isInTransitionWhere(
+ fromStatePredicate = { hiddenKeyguardStates.contains(it) },
+ toStatePredicate = { hiddenKeyguardStates.contains(it) },
+ )
+ .onStart { emit(false) },
+ ))
+ .any()
/** Last point that the root view was tapped */
val lastRootViewTapPosition: Flow<Point?> = keyguardInteractor.lastRootViewTapPosition
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 5d81d4f..9f9c8e9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -53,6 +53,10 @@
import com.android.compose.theme.PlatformTheme
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.dagger.MediaModule.QS_PANEL
+import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
import com.android.systemui.plugins.qs.QS
import com.android.systemui.plugins.qs.QSContainerController
import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel
@@ -65,6 +69,7 @@
import com.android.systemui.util.LifecycleFragment
import java.util.function.Consumer
import javax.inject.Inject
+import javax.inject.Named
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
@@ -77,6 +82,8 @@
@Inject
constructor(
private val qsFragmentComposeViewModelFactory: QSFragmentComposeViewModel.Factory,
+ @Named(QUICK_QS_PANEL) private val qqsMediaHost: MediaHost,
+ @Named(QS_PANEL) private val qsMediaHost: MediaHost,
) : LifecycleFragment(), QS {
private val scrollListener = MutableStateFlow<QS.ScrollListener?>(null)
@@ -99,6 +106,8 @@
QSComposeFragment.isUnexpectedlyInLegacyMode()
viewModel = qsFragmentComposeViewModelFactory.create(lifecycleScope)
+ qqsMediaHost.init(MediaHierarchyManager.LOCATION_QQS)
+ qsMediaHost.init(MediaHierarchyManager.LOCATION_QS)
setListenerCollections()
}
@@ -274,7 +283,7 @@
) {}
override fun isFullyCollapsed(): Boolean {
- return !viewModel.isQSVisible
+ return viewModel.qsExpansionValue <= 0f
}
override fun setCollapsedMediaVisibilityChangedListener(listener: Consumer<Boolean>?) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 9e109e4..7d52216 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -18,6 +18,7 @@
import android.content.res.Resources
import android.graphics.Rect
+import androidx.annotation.FloatRange
import androidx.lifecycle.LifecycleCoroutineScope
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.qualifiers.Main
@@ -79,11 +80,17 @@
_qsVisible.value = value
}
- private val _qsExpansion = MutableStateFlow(0f)
+ // This can only be negative if undefined (in which case it will be -1f), else it will be
+ // in [0, 1]. In some cases, it could be set back to -1f internally to indicate that it's
+ // different to every value in [0, 1].
+ @FloatRange(from = -1.0, to = 1.0) private val _qsExpansion = MutableStateFlow(-1f)
var qsExpansionValue: Float
get() = _qsExpansion.value
set(value) {
- _qsExpansion.value = value
+ if (value < 0f) {
+ _qsExpansion.value = -1f
+ }
+ _qsExpansion.value = value.coerceIn(0f, 1f)
}
private val _panelFraction = MutableStateFlow(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index dfcf216..ac6ebe7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -147,17 +147,17 @@
sealed interface State {
val isVisible: Boolean
- val expansion: Float
+ val expansion: () -> Float
val squishiness: () -> Float
data object CLOSED : State {
override val isVisible = false
- override val expansion = 0f
+ override val expansion = { 0f }
override val squishiness = { 1f }
}
/** State for expanding between QQS and QS */
- data class Expanding(override val expansion: Float) : State {
+ class Expanding(override val expansion: () -> Float) : State {
override val isVisible = true
override val squishiness = { 1f }
}
@@ -170,7 +170,7 @@
*/
class UnsquishingQQS(override val squishiness: () -> Float) : State {
override val isVisible = true
- override val expansion = 0f
+ override val expansion = { 0f }
}
/**
@@ -181,16 +181,16 @@
*/
class UnsquishingQS(override val squishiness: () -> Float) : State {
override val isVisible = true
- override val expansion = 1f
+ override val expansion = { 1f }
}
companion object {
// These are special cases of the expansion.
- val QQS = Expanding(0f)
- val QS = Expanding(1f)
+ val QQS = Expanding { 0f }
+ val QS = Expanding { 1f }
/** Collapsing from QS to QQS. [progress] is 0f in QS and 1f in QQS. */
- fun Collapsing(progress: Float) = Expanding(1f - progress)
+ fun Collapsing(progress: () -> Float) = Expanding { 1f - progress() }
}
}
}
@@ -418,14 +418,14 @@
private fun QSImpl.applyState(state: QSSceneAdapter.State) {
setQsVisible(state.isVisible)
- setExpanded(state.isVisible && state.expansion > 0f)
+ setExpanded(state.isVisible && state.expansion() > 0f)
setListening(state.isVisible)
}
override fun applyLatestExpansionAndSquishiness() {
val qsImpl = _qsImpl.value
val state = state.value
- qsImpl?.setQsExpansion(state.expansion, 1f, 0f, state.squishiness())
+ qsImpl?.setQsExpansion(state.expansion(), 1f, 0f, state.squishiness())
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 5885193..75cb017d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -28,8 +28,6 @@
import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.util.kotlin.getValue
-import com.android.systemui.util.kotlin.pairwiseBy
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -83,20 +81,7 @@
* Note that during a transition between scenes, more than one scene might be rendered but only
* one is considered the committed/current scene.
*/
- val currentScene: StateFlow<SceneKey> =
- repository.currentScene
- .pairwiseBy(initialValue = repository.currentScene.value) { from, to ->
- logger.logSceneChangeCommitted(
- from = from,
- to = to,
- )
- to
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = repository.currentScene.value,
- )
+ val currentScene: StateFlow<SceneKey> = repository.currentScene
/**
* The current state of the transition.
@@ -234,14 +219,15 @@
return
}
- logger.logSceneChangeRequested(
+ onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(resolvedScene, sceneState) }
+
+ logger.logSceneChanged(
from = currentSceneKey,
to = resolvedScene,
reason = loggingReason,
isInstant = false,
)
- onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(resolvedScene, sceneState) }
repository.changeScene(resolvedScene, transitionKey)
}
@@ -274,7 +260,7 @@
return
}
- logger.logSceneChangeRequested(
+ logger.logSceneChanged(
from = currentSceneKey,
to = resolvedScene,
reason = loggingReason,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
index cf1518e..94c94e2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
@@ -43,7 +43,7 @@
)
}
- fun logSceneChangeRequested(
+ fun logSceneChanged(
from: SceneKey,
to: SceneKey,
reason: String,
@@ -60,7 +60,7 @@
},
messagePrinter = {
buildString {
- append("Scene change requested: $str1 → $str2")
+ append("Scene changed: $str1 → $str2")
if (isInstant) {
append(" (instant)")
}
@@ -70,21 +70,6 @@
)
}
- fun logSceneChangeCommitted(
- from: SceneKey,
- to: SceneKey,
- ) {
- logBuffer.log(
- tag = TAG,
- level = LogLevel.INFO,
- messageInitializer = {
- str1 = from.toString()
- str2 = to.toString()
- },
- messagePrinter = { "Scene change committed: $str1 → $str2" },
- )
- }
-
fun logSceneTransition(transitionState: ObservableTransitionState) {
when (transitionState) {
is ObservableTransitionState.Transition -> {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 7f7f0f1..f8a9f8c 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -27,6 +27,7 @@
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.Scenes
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -42,6 +43,7 @@
private val sceneInteractor: SceneInteractor,
private val falsingInteractor: FalsingInteractor,
private val powerInteractor: PowerInteractor,
+ private val logger: SceneLogger,
@Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
) : SysUiViewModel() {
/**
@@ -135,16 +137,29 @@
else -> null
}
- return interactionTypeOrNull?.let { interactionType ->
- // It's important that the falsing system is always queried, even if no enforcement will
- // occur. This helps build up the right signal in the system.
- val isFalseTouch = falsingInteractor.isFalseTouch(interactionType)
+ val fromScene = currentScene.value
+ val isAllowed =
+ interactionTypeOrNull?.let { interactionType ->
+ // It's important that the falsing system is always queried, even if no enforcement
+ // will occur. This helps build up the right signal in the system.
+ val isFalseTouch = falsingInteractor.isFalseTouch(interactionType)
- // Only enforce falsing if moving from the lockscreen scene to a new scene.
- val fromLockscreenScene = currentScene.value == Scenes.Lockscreen
+ // Only enforce falsing if moving from the lockscreen scene to a new scene.
+ val fromLockscreenScene = fromScene == Scenes.Lockscreen
- !fromLockscreenScene || !isFalseTouch
- } ?: true
+ !fromLockscreenScene || !isFalseTouch
+ } ?: true
+
+ if (isAllowed) {
+ // A scene change is guaranteed; log it.
+ logger.logSceneChanged(
+ from = fromScene,
+ to = toScene,
+ reason = "user interaction",
+ isInstant = false,
+ )
+ }
+ return isAllowed
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 50ea3bb..448f7c4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -99,6 +99,9 @@
) {
val displays = getDisplaysToScreenshot(screenshotRequest.type)
val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
+ if (displays.isEmpty()) {
+ Log.wtf(TAG, "No displays found for screenshot.")
+ }
displays.forEach { display ->
val displayId = display.displayId
var screenshotHandler: ScreenshotHandler =
@@ -219,8 +222,7 @@
}
private fun getScreenshotController(display: Display): InteractiveScreenshotHandler {
- val controller =
- screenshotController ?: interactiveScreenshotHandlerFactory.create(display)
+ val controller = screenshotController ?: interactiveScreenshotHandlerFactory.create(display)
screenshotController = controller
return controller
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
index 3fe3162..29450a2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
@@ -26,7 +26,7 @@
import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult
import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
-import com.android.wm.shell.shared.desktopmode.DesktopModeFlags
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import javax.inject.Inject
import kotlinx.coroutines.flow.first
@@ -48,7 +48,7 @@
return NotMatched(policy = NAME, reason = SHADE_EXPANDED)
}
- if (DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context)) {
+ if (DesktopModeStatus.canEnterDesktopMode(context)) {
content.rootTasks.firstOrNull()?.also {
if (it.windowingMode == WINDOWING_MODE_FREEFORM) {
return NotMatched(policy = NAME, reason = DESKTOP_MODE_ENABLED)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
index ee1944e..ad27da9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
@@ -214,8 +214,7 @@
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
- if (mCurrentDraggingBoundary != CropBoundary.NONE
- && mActivePointerId == event.getPointerId(mActivePointerId)) {
+ if (mCurrentDraggingBoundary != CropBoundary.NONE) {
updateListener(MotionEvent.ACTION_UP, event.getX(0));
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 7c3072d..6a2c602 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -139,6 +139,9 @@
/** Fraction of shade expansion. */
private float mExpansionFraction;
+ /** Fraction of QS expansion. 0 when in shade, 1 when in QS. */
+ private float mQsExpansionFraction;
+
/** Height of the notifications panel when expansion completes. */
private float mStackEndHeight;
@@ -208,6 +211,14 @@
}
/**
+ * @param expansionFraction Fraction of QS expansion.
+ */
+ public void setQsExpansionFraction(float expansionFraction) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ mQsExpansionFraction = expansionFraction;
+ }
+
+ /**
* @param isSwipingUp Whether we are swiping up.
*/
public void setSwipingUp(boolean isSwipingUp) {
@@ -258,6 +269,14 @@
}
/**
+ * @return Fraction of QS expansion.
+ */
+ public float getQsExpansionFraction() {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f;
+ return mQsExpansionFraction;
+ }
+
+ /**
* @see #getStackHeight()
*/
public void setStackHeight(float stackHeight) {
@@ -837,6 +856,7 @@
pw.println("mAppearFraction=" + mAppearFraction);
pw.println("mAppearing=" + mAppearing);
pw.println("mExpansionFraction=" + mExpansionFraction);
+ pw.println("mQsExpansionFraction=" + mQsExpansionFraction);
pw.println("mExpandingVelocity=" + mExpandingVelocity);
pw.println("mOverScrollTopAmount=" + mOverScrollTopAmount);
pw.println("mOverScrollBottomAmount=" + mOverScrollBottomAmount);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 07b0b83..f26f840 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1244,6 +1244,7 @@
@Override
public void setHeadsUpTop(float headsUpTop) {
mAmbientState.setHeadsUpTop(headsUpTop);
+ requestChildrenUpdate();
}
@Override
@@ -1563,6 +1564,12 @@
}
}
+ @Override
+ public void setQsExpandFraction(float expandFraction) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ mAmbientState.setQsExpansionFraction(expandFraction);
+ }
+
/**
* Update the height of the panel.
*
@@ -2534,6 +2541,11 @@
return getTopHeadsUpIntrinsicHeight();
}
+ @Override
+ public int getHeadsUpInset() {
+ return mHeadsUpInset;
+ }
+
/**
* Calculate the gap height between two different views
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index f35d666..55f0566 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -132,6 +132,7 @@
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpNotificationViewControllerEmptyImpl;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.phone.HeadsUpTouchHelper.HeadsUpNotificationViewController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -773,7 +774,7 @@
mHeadsUpManager,
statusBarService.get(),
getHeadsUpCallback(),
- new HeadsUpNotificationViewControllerEmptyImpl()
+ getHeadsUpNotificationViewController()
);
}
mNotificationRoundnessManager = notificationRoundnessManager;
@@ -1851,6 +1852,32 @@
return mTouchHandler;
}
+ private HeadsUpNotificationViewController getHeadsUpNotificationViewController() {
+ HeadsUpNotificationViewController headsUpViewController;
+ if (SceneContainerFlag.isEnabled()) {
+ headsUpViewController = new HeadsUpNotificationViewController() {
+ @Override
+ public void setHeadsUpDraggingStartingHeight(int startHeight) {
+ // do nothing
+ }
+
+ @Override
+ public void setTrackedHeadsUp(ExpandableNotificationRow expandableNotificationRow) {
+ setTrackingHeadsUp(expandableNotificationRow);
+ }
+
+ @Override
+ public void startExpand(float newX, float newY, boolean startTracking,
+ float expandedHeight) {
+ // do nothing
+ }
+ };
+ } else {
+ headsUpViewController = new HeadsUpNotificationViewControllerEmptyImpl();
+ }
+ return headsUpViewController;
+ }
+
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("mMaxAlphaFromView=" + mMaxAlphaFromView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index aee1d3e..0c2b5ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.notification.stack;
-import static androidx.core.math.MathUtils.clamp;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -890,7 +888,14 @@
continue;
}
ExpandableViewState childState = row.getViewState();
- if (topHeadsUpEntry == null && row.mustStayOnScreen() && !childState.headsUpIsVisible) {
+ boolean shouldSetTopHeadsUpEntry;
+ if (SceneContainerFlag.isEnabled()) {
+ shouldSetTopHeadsUpEntry = row.isHeadsUp();
+ } else {
+ shouldSetTopHeadsUpEntry = row.mustStayOnScreen();
+ }
+ if (topHeadsUpEntry == null && shouldSetTopHeadsUpEntry
+ && !childState.headsUpIsVisible) {
topHeadsUpEntry = row;
childState.location = ExpandableViewState.LOCATION_FIRST_HUN;
}
@@ -898,7 +903,7 @@
float unmodifiedEndLocation = childState.getYTranslation() + childState.height;
if (mIsExpanded) {
if (SceneContainerFlag.isEnabled()) {
- if (shouldHunBeVisibleWhenScrolled(row.mustStayOnScreen(),
+ if (shouldHunBeVisibleWhenScrolled(row.isHeadsUp(),
childState.headsUpIsVisible, row.showingPulsing(),
ambientState.isOnKeyguard(), row.getEntry().isStickyAndNotDemoted())) {
// the height of this child before clamping it to the top
@@ -909,10 +914,19 @@
/* viewState = */ childState
);
float baseZ = ambientState.getBaseZHeight();
- if (headsUpTranslation < ambientState.getStackTop()) {
- // HUN displayed above the stack top, it needs a fix shadow
- childState.setZTranslation(baseZ + mPinnedZTranslationExtra);
- } else {
+ if (headsUpTranslation > ambientState.getStackTop()
+ && row.isAboveShelf()) {
+ // HUN displayed outside of the stack during transition from Gone/LS;
+ // add a shadow that corresponds to the transition progress.
+ float fraction = 1 - ambientState.getExpansionFraction();
+ childState.setZTranslation(baseZ + fraction * mPinnedZTranslationExtra);
+ } else if (headsUpTranslation < ambientState.getStackTop()
+ && row.isAboveShelf()) {
+ // HUN displayed outside of the stack during transition from QS;
+ // add a shadow that corresponds to the transition progress.
+ float fraction = ambientState.getQsExpansionFraction();
+ childState.setZTranslation(baseZ + fraction * mPinnedZTranslationExtra);
+ } else if (headsUpTranslation > ambientState.getStackTop()) {
// HUN displayed within the stack, add a shadow if it overlaps with
// other elements.
//
@@ -927,6 +941,8 @@
/* baseZ = */ baseZ,
/* viewState = */ childState
);
+ } else {
+ childState.setZTranslation(baseZ);
}
if (isTopEntry && row.isAboveShelf()) {
clampHunToMaxTranslation(
@@ -1081,7 +1097,7 @@
if (scrollingContentTopPadding > 0f) {
// scrollingContentTopPadding makes a gap between the bottom of the HUN and the top
// of the scrolling content. Use this to animate to the full shadow.
- shadowFraction = clamp(overlap / scrollingContentTopPadding, 0f, 1f);
+ shadowFraction = Math.clamp(overlap / scrollingContentTopPadding, 0f, 1f);
}
if (overlap > 0.0f) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index 6226fe7..288924d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -80,9 +80,15 @@
/** sets the current expand fraction */
fun setExpandFraction(expandFraction: Float)
+ /** sets the current QS expand fraction */
+ fun setQsExpandFraction(expandFraction: Float)
+
/** Sets whether the view is displayed in doze mode. */
fun setDozing(dozing: Boolean)
+ /** Gets the inset for HUNs when they are not visible */
+ fun getHeadsUpInset(): Int
+
/** Adds a listener to be notified, when the stack height might have changed. */
fun addStackHeightChangedListener(runnable: Runnable)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 950b14d..40761e07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -85,9 +85,14 @@
launch {
viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) }
}
+ launch { viewModel.qsExpandFraction.collect { view.setQsExpandFraction(it) } }
launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } }
launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } }
- launch { viewModel.shouldResetStackTop.filter { it }.collect { view.setStackTop(0f) } }
+ launch {
+ viewModel.shouldResetStackTop
+ .filter { it }
+ .collect { view.setStackTop(-(view.getHeadsUpInset().toFloat())) }
+ }
launchAndDispose {
view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index ed69e6f..6489264 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -124,6 +124,9 @@
.distinctUntilChanged()
.dumpWhileCollecting("expandFraction")
+ val qsExpandFraction: Flow<Float> =
+ shadeInteractor.qsExpansion.dumpWhileCollecting("qsExpandFraction")
+
val shouldResetStackTop: Flow<Boolean> =
sceneInteractor.transitionState
.mapNotNull { state ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index 7586133..f16fcb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -42,6 +42,7 @@
class ZenModeInteractor
@Inject
constructor(
+ private val context: Context,
private val zenModeRepository: ZenModeRepository,
private val notificationSettingsRepository: NotificationSettingsRepository,
) {
@@ -78,6 +79,26 @@
val activeModes: Flow<List<ZenMode>> =
modes.map { modes -> modes.filter { mode -> mode.isActive } }.distinctUntilChanged()
+ /** Flow returning the most prioritized of the active modes, if any. */
+ val mainActiveMode: Flow<ZenMode?> =
+ activeModes.map { modes -> getMainActiveMode(modes) }.distinctUntilChanged()
+
+ /**
+ * Given the list of modes (which may include zero or more currently active modes), returns the
+ * most prioritized of the active modes, if any.
+ */
+ private fun getMainActiveMode(modes: List<ZenMode>): ZenMode? {
+ return modes.sortedWith(ZenMode.PRIORITIZING_COMPARATOR).firstOrNull { it.isActive }
+ }
+
+ suspend fun getModeIcon(mode: ZenMode): Icon {
+ return mode.getIcon(context, iconLoader).await().asIcon()
+ }
+
+ suspend fun getLockscreenModeIcon(mode: ZenMode): Icon {
+ return mode.getLockscreenIcon(context, iconLoader).await().asIcon()
+ }
+
/**
* Given the list of modes (which may include zero or more currently active modes), returns an
* icon representing the active mode, if any (or, if multiple modes are active, to the most
@@ -86,16 +107,7 @@
* package).
*/
suspend fun getActiveModeIcon(context: Context, modes: List<ZenMode>): Icon? {
- return modes
- .sortedWith(ZenMode.PRIORITIZING_COMPARATOR)
- .firstOrNull { it.isActive }
- ?.getLockscreenIcon(context, iconLoader)
- ?.await()
- ?.asIcon()
- }
-
- suspend fun getModeIcon(context: Context, mode: ZenMode): Icon {
- return mode.getIcon(context, iconLoader).await().asIcon()
+ return getMainActiveMode(modes)?.let { m -> getLockscreenModeIcon(m) }
}
fun activateMode(zenMode: ZenMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index 02b5e49..be90bec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -88,7 +88,7 @@
modesList.map { mode ->
ModeTileViewModel(
id = mode.id,
- icon = zenModeInteractor.getModeIcon(context, mode),
+ icon = zenModeInteractor.getModeIcon(mode),
text = mode.name,
subtext = getTileSubtext(mode),
enabled = mode.isActive,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index c425e82..5fc1971 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -18,6 +18,7 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static com.android.systemui.Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED;
@@ -26,7 +27,6 @@
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_MINIMIZED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
-import static com.android.systemui.flags.Flags.CLIPBOARD_SHARED_TRANSITIONS;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -47,6 +47,8 @@
import android.graphics.Rect;
import android.net.Uri;
import android.os.PersistableBundle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.view.WindowInsets;
import android.view.textclassifier.TextLinks;
@@ -130,7 +132,6 @@
new ClipData.Item("Test Item"));
mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, true); // turned off for legacy tests
- mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, true); // turned off for old tests
}
/**
@@ -299,8 +300,8 @@
}
@Test
+ @DisableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
public void test_viewCallbacks_onShareTapped_sharedTransitionsOff() {
- mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
initController();
mOverlayController.setClipData(mSampleClipData, "");
@@ -311,6 +312,7 @@
}
@Test
+ @EnableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
public void test_viewCallbacks_onShareTapped() {
initController();
mOverlayController.setClipData(mSampleClipData, "");
@@ -324,8 +326,8 @@
}
@Test
+ @DisableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
public void test_viewCallbacks_onDismissTapped_sharedTransitionsOff() {
- mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
initController();
mOverlayController.setClipData(mSampleClipData, "");
@@ -336,6 +338,7 @@
}
@Test
+ @EnableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
public void test_viewCallbacks_onDismissTapped() {
initController();
@@ -350,7 +353,6 @@
@Test
public void test_multipleDismissals_dismissesOnce_sharedTransitionsOff() {
- mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
initController();
mCallbacks.onSwipeDismissInitiated(mAnimator);
mCallbacks.onDismissButtonTapped();
@@ -362,6 +364,7 @@
}
@Test
+ @EnableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
public void test_multipleDismissals_dismissesOnce() {
initController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
index c2f035f1..19735e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
@@ -93,7 +93,7 @@
private val tileDataInteractor =
ModesTileDataInteractor(
context,
- ZenModeInteractor(zenModeRepository, mock<NotificationSettingsRepository>()),
+ ZenModeInteractor(context, zenModeRepository, mock<NotificationSettingsRepository>()),
testDispatcher
)
private val mapper =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
index a5fbfb5..be9fcc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
@@ -18,11 +18,13 @@
import android.content.ComponentName
import android.content.Context
+import android.content.res.Resources
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.internal.R
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.screenshot.data.model.DisplayContentModel
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
@@ -57,6 +59,7 @@
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class WorkProfilePolicyTest {
@@ -66,12 +69,17 @@
@JvmField @Rule(order = 2) val mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock lateinit var mContext: Context
+ @Mock lateinit var mResources: Resources
private val kosmos = Kosmos()
private lateinit var policy: WorkProfilePolicy
@Before
fun setUp() {
+ // Set desktop mode supported
+ whenever(mContext.resources).thenReturn(mResources)
+ whenever(mResources.getBoolean(R.bool.config_isDesktopModeSupported)).thenReturn(true)
+
policy = WorkProfilePolicy(kosmos.profileTypeRepository, mContext)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index ad029d7..7e79019 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -296,6 +296,7 @@
collapsedHeight = 100,
intrinsicHeight = intrinsicHunHeight,
)
+ ambientState.qsExpansionFraction = 1.0f
whenever(notificationRow.isAboveShelf).thenReturn(true)
// When
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
index aef0828..66be7e7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy.domain.interactor
+import android.content.testableContext
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository
@@ -23,6 +24,7 @@
val Kosmos.zenModeInteractor by Fixture {
ZenModeInteractor(
+ context = testableContext,
zenModeRepository = zenModeRepository,
notificationSettingsRepository = notificationSettingsRepository,
)
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 9c6bcdf..53885fc 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -140,7 +140,7 @@
bindAppFunctionServiceUnchecked(requestInternal, serviceIntent, targetUser,
safeExecuteAppFunctionCallback,
/*bindFlags=*/ Context.BIND_AUTO_CREATE,
- /*timeoutInMillis=*/ mServiceConfig.getExecutionTimeoutConfig());
+ /*timeoutInMillis=*/ mServiceConfig.getExecuteAppFunctionTimeoutMillis());
}
private void bindAppFunctionServiceUnchecked(
diff --git a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java
index 35c6c78..4bc6e70 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java
@@ -26,5 +26,5 @@
/**
* Returns the maximum time to wait for an app function execution to be complete.
*/
- long getExecutionTimeoutConfig();
+ long getExecuteAppFunctionTimeoutMillis();
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java
index b203ead..e090317 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java
@@ -22,16 +22,17 @@
* Implementation of {@link ServiceConfig}
*/
public class ServiceConfigImpl implements ServiceConfig {
- static final String DEVICE_CONFIG_PROPERTY_EXECUTION_TIMEOUT = "execution_timeout";
- static final long DEFAULT_EXECUTION_TIMEOUT_MS = 5000L;
+ static final String DEVICE_CONFIG_PROPERTY_EXECUTION_TIMEOUT =
+ "execute_app_function_timeout_millis";
+ static final long DEFAULT_EXECUTE_APP_FUNCTION_TIMEOUT_MS = 5000L;
@Override
- public long getExecutionTimeoutConfig() {
+ public long getExecuteAppFunctionTimeoutMillis() {
return DeviceConfig.getLong(
NAMESPACE_APP_FUNCTIONS,
DEVICE_CONFIG_PROPERTY_EXECUTION_TIMEOUT,
- DEFAULT_EXECUTION_TIMEOUT_MS
+ DEFAULT_EXECUTE_APP_FUNCTION_TIMEOUT_MS
);
}
}
diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
index 8abbe56..22eefb3 100644
--- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
+++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
@@ -73,6 +73,8 @@
/**
* Utility methods to read backup tar file.
+ * Exteranl depenency:
+ * <li> @android.provider.Settings.Secure.V_TO_U_RESTORE_ALLOWLIST
*/
public class TarBackupReader {
private static final int TAR_HEADER_OFFSET_TYPE_CHAR = 156;
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index b3a2da4..d56f17b 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -20,6 +20,7 @@
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
+import static android.companion.CompanionDeviceManager.RESULT_SECURITY_ERROR;
import static android.content.ComponentName.createRelative;
import static android.content.pm.PackageManager.FEATURE_WATCH;
@@ -40,6 +41,7 @@
import android.companion.AssociatedDevice;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
+import android.companion.Flags;
import android.companion.IAssociationRequestCallback;
import android.content.ComponentName;
import android.content.Context;
@@ -182,7 +184,11 @@
String errorMessage = "3p apps are not allowed to create associations on watch.";
Slog.e(TAG, errorMessage);
try {
- callback.onFailure(RESULT_INTERNAL_ERROR);
+ if (Flags.associationFailureCode()) {
+ callback.onFailure(RESULT_SECURITY_ERROR, errorMessage);
+ } else {
+ callback.onFailure(RESULT_INTERNAL_ERROR, errorMessage);
+ }
} catch (RemoteException e) {
// ignored
}
@@ -251,9 +257,12 @@
} catch (SecurityException e) {
// Since, at this point the caller is our own UI, we need to catch the exception on
// forward it back to the application via the callback.
- Slog.e(TAG, e.getMessage());
try {
- callback.onFailure(RESULT_INTERNAL_ERROR);
+ if (Flags.associationFailureCode()) {
+ callback.onFailure(RESULT_SECURITY_ERROR, e.getMessage());
+ } else {
+ callback.onFailure(RESULT_INTERNAL_ERROR, e.getMessage());
+ }
} catch (RemoteException ignore) {
}
return;
@@ -378,7 +387,7 @@
// Send the association back via the app's callback
if (callback != null) {
try {
- callback.onFailure(RESULT_INTERNAL_ERROR);
+ callback.onFailure(RESULT_INTERNAL_ERROR, "Association doesn't exist.");
} catch (RemoteException ignore) {
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 8da58cf..d3e808f 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -689,13 +689,15 @@
}
/** A helper class used to wait for an input device to be registered. */
- private class WaitForDevice implements AutoCloseable {
+ private class WaitForDevice implements AutoCloseable {
private final CountDownLatch mDeviceAddedLatch = new CountDownLatch(1);
+ private final String mDeviceName;
private final InputManager.InputDeviceListener mListener;
private int mInputDeviceId = IInputConstants.INVALID_INPUT_DEVICE_ID;
WaitForDevice(String deviceName, int vendorId, int productId, int associatedDisplayId) {
+ mDeviceName = deviceName;
mListener = new InputManager.InputDeviceListener() {
@Override
public void onInputDeviceAdded(int deviceId) {
@@ -741,15 +743,17 @@
try {
if (!mDeviceAddedLatch.await(1, TimeUnit.MINUTES)) {
throw new DeviceCreationException(
- "Timed out waiting for virtual device to be created.");
+ "Timed out waiting for virtual input device " + mDeviceName
+ + " to be created.");
}
} catch (InterruptedException e) {
throw new DeviceCreationException(
- "Interrupted while waiting for virtual device to be created.", e);
+ "Interrupted while waiting for virtual input device " + mDeviceName
+ + " to be created.", e);
}
if (mInputDeviceId == IInputConstants.INVALID_INPUT_DEVICE_ID) {
throw new IllegalStateException(
- "Virtual input device was created with an invalid "
+ "Virtual input device " + mDeviceName + " was created with an invalid "
+ "id=" + mInputDeviceId);
}
return mInputDeviceId;
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index e84250d..47203fb 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -296,7 +296,9 @@
this::onSyncRequestNotified);
setPropertyChangedListenerLocked();
updateConfigs();
- registerConnectivityModuleHealthListener();
+ if (!Flags.refactorCrashrecovery()) {
+ registerConnectivityModuleHealthListener();
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 68d9221..f1bdc05 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -50,7 +50,6 @@
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
-import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
@@ -101,15 +100,12 @@
import static android.os.Process.BLUETOOTH_UID;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.INVALID_UID;
-import static android.os.Process.NETWORK_STACK_UID;
-import static android.os.Process.NFC_UID;
import static android.os.Process.PHONE_UID;
import static android.os.Process.PROC_OUT_LONG;
import static android.os.Process.PROC_SPACE_TERM;
import static android.os.Process.ROOT_UID;
import static android.os.Process.SCHED_FIFO;
import static android.os.Process.SCHED_RESET_ON_FORK;
-import static android.os.Process.SE_UID;
import static android.os.Process.SHELL_UID;
import static android.os.Process.SIGNAL_USR1;
import static android.os.Process.SYSTEM_UID;
@@ -145,8 +141,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALLOWLISTS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
@@ -155,7 +149,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LRU;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
@@ -265,10 +258,7 @@
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
-import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetManagerInternal;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
import android.content.AttributionSource;
import android.content.AutofillOptions;
import android.content.BroadcastReceiver;
@@ -316,9 +306,6 @@
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
-import android.media.audiofx.AudioEffect;
-import android.net.ConnectivityManager;
-import android.net.Proxy;
import android.net.Uri;
import android.os.AppZygote;
import android.os.BatteryStats;
@@ -374,9 +361,7 @@
import android.server.ServerProtoEnums;
import android.system.Os;
import android.system.OsConstants;
-import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.text.style.SuggestionSpan;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
@@ -386,7 +371,6 @@
import android.util.Log;
import android.util.MathUtils;
import android.util.Pair;
-import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -435,11 +419,11 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.MemInfoReader;
import com.android.internal.util.Preconditions;
+import com.android.server.crashrecovery.CrashRecoveryHelper;
import com.android.server.AlarmManagerInternal;
import com.android.server.BootReceiver;
import com.android.server.DeviceIdleInternal;
import com.android.server.DisplayThread;
-import com.android.server.IntentResolver;
import com.android.server.IoThread;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
@@ -451,7 +435,6 @@
import com.android.server.SystemServiceManager;
import com.android.server.ThreadPriorityBooster;
import com.android.server.Watchdog;
-import com.android.server.am.ComponentAliasResolver.Resolution;
import com.android.server.am.LowMemDetector.MemFactor;
import com.android.server.appop.AppOpsService;
import com.android.server.compat.PlatformCompat;
@@ -462,14 +445,12 @@
import com.android.server.job.JobSchedulerInternal;
import com.android.server.net.NetworkManagementInternal;
import com.android.server.os.NativeTombstoneManager;
-import com.android.server.pm.Computer;
import com.android.server.pm.Installer;
import com.android.server.pm.SaferIntentUtils;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.SELinuxUtil;
-import com.android.server.pm.snapshot.PackageDataSnapshot;
import com.android.server.power.stats.BatteryStatsImpl;
import com.android.server.sdksandbox.SdkSandboxManagerLocal;
import com.android.server.stats.pull.StatsPullAtomService;
@@ -512,7 +493,6 @@
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@@ -537,7 +517,6 @@
static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM;
static final String TAG_BACKUP = TAG + POSTFIX_BACKUP;
- private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
private static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP;
private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
@@ -565,9 +544,6 @@
static final String SYSTEM_USER_HOME_NEEDED = "ro.system_user_home_needed";
- // Maximum number of receivers an app can register.
- private static final int MAX_RECEIVERS_ALLOWED_PER_APP = 1000;
-
// How long we wait for a launched process to attach to the activity manager
// before we decide it's never going to come up for real.
static final int PROC_START_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
@@ -652,15 +628,6 @@
static final String EXTRA_BUGREPORT_NONCE = "android.intent.extra.BUGREPORT_NONCE";
static final String EXTRA_EXTRA_ATTACHMENT_URI =
"android.intent.extra.EXTRA_ATTACHMENT_URI";
- /**
- * It is now required for apps to explicitly set either
- * {@link android.content.Context#RECEIVER_EXPORTED} or
- * {@link android.content.Context#RECEIVER_NOT_EXPORTED} when registering a receiver for an
- * unprotected broadcast in code.
- */
- @ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- private static final long DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED = 161145287L;
/**
* The maximum number of bytes that {@link #setProcessStateSummary} accepts.
@@ -737,11 +704,9 @@
// so that dispatch of foreground broadcasts gets precedence.
private BroadcastQueue mBroadcastQueue;
- @GuardedBy("this")
- BroadcastStats mLastBroadcastStats;
-
- @GuardedBy("this")
- BroadcastStats mCurBroadcastStats;
+ // TODO: Add a consistent way of accessing the methods within this class. Currently, some
+ // methods require access while holding a lock, while others do not.
+ BroadcastController mBroadcastController;
TraceErrorLogger mTraceErrorLogger;
@@ -763,6 +728,7 @@
final AppErrors mAppErrors;
final PackageWatchdog mPackageWatchdog;
+ final CrashRecoveryHelper mCrashRecoveryHelper;
@GuardedBy("mDeliveryGroupPolicyIgnoredActions")
private final ArraySet<String> mDeliveryGroupPolicyIgnoredActions = new ArraySet();
@@ -860,12 +826,6 @@
};
/**
- * Broadcast actions that will always be deliverable to unlaunched/background apps
- */
- @GuardedBy("this")
- private ArraySet<String> mBackgroundLaunchBroadcasts;
-
- /**
* When an app has restrictions on the other apps that can have associations with it,
* it appears here with a set of the allowed apps and also track debuggability of the app.
*/
@@ -1133,97 +1093,6 @@
private final HashSet<Integer> mAlreadyLoggedViolatedStacks = new HashSet<Integer>();
private static final int MAX_DUP_SUPPRESSED_STACKS = 5000;
- /**
- * Keeps track of all IIntentReceivers that have been registered for broadcasts.
- * Hash keys are the receiver IBinder, hash value is a ReceiverList.
- */
- @GuardedBy("this")
- final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
-
- /**
- * Resolver for broadcast intents to registered receivers.
- * Holds BroadcastFilter (subclass of IntentFilter).
- */
- final IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver
- = new IntentResolver<BroadcastFilter, BroadcastFilter>() {
- @Override
- protected boolean allowFilterResult(
- BroadcastFilter filter, List<BroadcastFilter> dest) {
- IBinder target = filter.receiverList.receiver.asBinder();
- for (int i = dest.size() - 1; i >= 0; i--) {
- if (dest.get(i).receiverList.receiver.asBinder() == target) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- protected BroadcastFilter newResult(@NonNull Computer computer, BroadcastFilter filter,
- int match, int userId, long customFlags) {
- if (userId == UserHandle.USER_ALL || filter.owningUserId == UserHandle.USER_ALL
- || userId == filter.owningUserId) {
- return super.newResult(computer, filter, match, userId, customFlags);
- }
- return null;
- }
-
- @Override
- protected IntentFilter getIntentFilter(@NonNull BroadcastFilter input) {
- return input;
- }
-
- @Override
- protected BroadcastFilter[] newArray(int size) {
- return new BroadcastFilter[size];
- }
-
- @Override
- protected boolean isPackageForFilter(String packageName, BroadcastFilter filter) {
- return packageName.equals(filter.packageName);
- }
- };
-
- /**
- * State of all active sticky broadcasts per user. Keys are the action of the
- * sticky Intent, values are an ArrayList of all broadcasted intents with
- * that action (which should usually be one). The SparseArray is keyed
- * by the user ID the sticky is for, and can include UserHandle.USER_ALL
- * for stickies that are sent to all users.
- */
- @GuardedBy("mStickyBroadcasts")
- final SparseArray<ArrayMap<String, ArrayList<StickyBroadcast>>> mStickyBroadcasts =
- new SparseArray<>();
-
- @VisibleForTesting
- static final class StickyBroadcast {
- public Intent intent;
- public boolean deferUntilActive;
- public int originalCallingUid;
- /** The snapshot process state of the app who sent this broadcast */
- public int originalCallingAppProcessState;
- public String resolvedDataType;
-
- public static StickyBroadcast create(Intent intent, boolean deferUntilActive,
- int originalCallingUid, int originalCallingAppProcessState,
- String resolvedDataType) {
- final StickyBroadcast b = new StickyBroadcast();
- b.intent = intent;
- b.deferUntilActive = deferUntilActive;
- b.originalCallingUid = originalCallingUid;
- b.originalCallingAppProcessState = originalCallingAppProcessState;
- b.resolvedDataType = resolvedDataType;
- return b;
- }
-
- @Override
- public String toString() {
- return "{intent=" + intent + ", defer=" + deferUntilActive + ", originalCallingUid="
- + originalCallingUid + ", originalCallingAppProcessState="
- + originalCallingAppProcessState + ", type=" + resolvedDataType + "}";
- }
- }
-
final ActiveServices mServices;
final static class Association {
@@ -1685,7 +1554,7 @@
// Encapsulates the global setting "hidden_api_blacklist_exemptions"
final HiddenApiSettings mHiddenApiBlacklist;
- private final PlatformCompat mPlatformCompat;
+ final PlatformCompat mPlatformCompat;
PackageManagerInternal mPackageManagerInt;
PermissionManagerServiceInternal mPermissionManagerInt;
@@ -2326,10 +2195,12 @@
mService.mBatteryStatsService.systemServicesReady();
mService.mServices.systemServicesReady();
} else if (phase == PHASE_ACTIVITY_MANAGER_READY) {
- mService.startBroadcastObservers();
+ mService.mBroadcastController.startBroadcastObservers();
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
if (!refactorCrashrecovery()) {
mService.mPackageWatchdog.onPackagesReady();
+ } else {
+ mService.mCrashRecoveryHelper.registerConnectivityModuleHealthListener();
}
mService.scheduleHomeTimeout();
}
@@ -2500,6 +2371,7 @@
mUiContext = null;
mAppErrors = injector.getAppErrors();
mPackageWatchdog = null;
+ mCrashRecoveryHelper = null;
mAppOpsService = mInjector.getAppOpsService(null /* recentAccessesFile */,
null /* storageFile */, null /* handler */);
mBatteryStatsService = mInjector.getBatteryStatsService();
@@ -2537,6 +2409,7 @@
mPendingStartActivityUids = new PendingStartActivityUids();
mUseFifoUiScheduling = false;
mBroadcastQueue = injector.getBroadcastQueue(this);
+ mBroadcastController = new BroadcastController(mContext, this, mBroadcastQueue);
mComponentAliasResolver = new ComponentAliasResolver(this);
}
@@ -2579,9 +2452,11 @@
: new OomAdjuster(this, mProcessList, activeUids);
mBroadcastQueue = mInjector.getBroadcastQueue(this);
+ mBroadcastController = new BroadcastController(mContext, this, mBroadcastQueue);
mServices = new ActiveServices(this);
mCpHelper = new ContentProviderHelper(this, true);
+ mCrashRecoveryHelper = new CrashRecoveryHelper(mUiContext);
mPackageWatchdog = PackageWatchdog.getInstance(mUiContext);
mAppErrors = new AppErrors(mUiContext, this, mPackageWatchdog);
mUidObserverController = new UidObserverController(mUiHandler);
@@ -2646,6 +2521,7 @@
void setBroadcastQueueForTest(BroadcastQueue broadcastQueue) {
mBroadcastQueue = broadcastQueue;
+ mBroadcastController.setBroadcastQueueForTest(broadcastQueue);
}
BroadcastQueue getBroadcastQueue() {
@@ -2680,18 +2556,6 @@
mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
}
- private ArraySet<String> getBackgroundLaunchBroadcasts() {
- if (mBackgroundLaunchBroadcasts == null) {
- mBackgroundLaunchBroadcasts = SystemConfig.getInstance().getAllowImplicitBroadcasts();
- }
- return mBackgroundLaunchBroadcasts;
- }
-
- private String getWearRemoteIntentAction() {
- return mContext.getResources().getString(
- com.android.internal.R.string.config_wearRemoteIntentAction);
- }
-
/**
* Ensures that the given package name has an explicit set of allowed associations.
* If it does not, give it an empty set.
@@ -2762,7 +2626,7 @@
}
/** Updates allowed associations for app info (specifically, based on debuggability). */
- private void updateAssociationForApp(ApplicationInfo appInfo) {
+ void updateAssociationForApp(ApplicationInfo appInfo) {
ensureAllowedAssociations();
PackageAssociationInfo pai = mAllowedAssociations.get(appInfo.packageName);
if (pai != null) {
@@ -3888,7 +3752,7 @@
forceStopPackage(packageName, userId, ActivityManager.FLAG_OR_STOPPED, null);
}
- private void forceStopPackage(final String packageName, int userId, int userRunningFlags,
+ void forceStopPackage(final String packageName, int userId, int userRunningFlags,
String reason) {
if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
!= PackageManager.PERMISSION_GRANTED) {
@@ -4216,7 +4080,7 @@
mPackageManagerInt.sendPackageRestartedBroadcast(packageName, uid, flags);
}
- private void cleanupDisabledPackageComponentsLocked(
+ void cleanupDisabledPackageComponentsLocked(
String packageName, int userId, String[] changedClasses) {
Set<String> disabledClasses = null;
@@ -4454,9 +4318,7 @@
if (packageName == null) {
// Remove all sticky broadcasts from this user.
- synchronized (mStickyBroadcasts) {
- mStickyBroadcasts.remove(userId);
- }
+ mBroadcastController.removeStickyBroadcasts(userId);
}
ArrayList<ContentProviderRecord> providers = new ArrayList<>();
@@ -9329,10 +9191,6 @@
Settings.Global.DEVICE_PROVISIONED, 0) != 0;
}
- private void startBroadcastObservers() {
- mBroadcastQueue.start(mContext.getContentResolver());
- }
-
private void updateForceBackgroundCheck(boolean enabled) {
synchronized (this) {
synchronized (mProcLock) {
@@ -10524,14 +10382,15 @@
pw.println(
"-------------------------------------------------------------------------------");
}
- dumpBroadcastsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+ mBroadcastController.dumpBroadcastsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
pw.println(
"-------------------------------------------------------------------------------");
}
if (dumpAll || dumpPackage != null) {
- dumpBroadcastStatsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+ mBroadcastController.dumpBroadcastStatsLocked(fd, pw, args, opti, dumpAll,
+ dumpPackage);
pw.println();
if (dumpAll) {
pw.println(
@@ -10782,7 +10641,7 @@
} else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
// output proto is ActivityManagerServiceDumpBroadcastsProto
synchronized (this) {
- writeBroadcastsToProtoLocked(proto);
+ mBroadcastController.writeBroadcastsToProtoLocked(proto);
}
} else if ("provider".equals(cmd)) {
String[] newArgs;
@@ -10846,7 +10705,7 @@
proto.end(activityToken);
long broadcastToken = proto.start(ActivityManagerServiceProto.BROADCASTS);
- writeBroadcastsToProtoLocked(proto);
+ mBroadcastController.writeBroadcastsToProtoLocked(proto);
proto.end(broadcastToken);
long serviceToken = proto.start(ActivityManagerServiceProto.SERVICES);
@@ -10906,7 +10765,8 @@
opti++;
}
synchronized (this) {
- dumpBroadcastsLocked(fd, pw, args, opti, /* dumpAll= */ true, dumpPackage);
+ mBroadcastController.dumpBroadcastsLocked(fd, pw, args, opti,
+ /* dumpAll= */ true, dumpPackage);
}
} else if ("broadcast-stats".equals(cmd)) {
if (opti < args.length) {
@@ -10915,10 +10775,11 @@
}
synchronized (this) {
if (dumpCheckinFormat) {
- dumpBroadcastStatsCheckinLocked(fd, pw, args, opti, dumpCheckin,
- dumpPackage);
+ mBroadcastController.dumpBroadcastStatsCheckinLocked(fd, pw, args, opti,
+ dumpCheckin, dumpPackage);
} else {
- dumpBroadcastStatsLocked(fd, pw, args, opti, true, dumpPackage);
+ mBroadcastController.dumpBroadcastStatsLocked(fd, pw, args, opti, true,
+ dumpPackage);
}
}
} else if ("intents".equals(cmd) || "i".equals(cmd)) {
@@ -11072,7 +10933,8 @@
// No piece of data specified, dump everything.
if (dumpCheckinFormat) {
- dumpBroadcastStatsCheckinLocked(fd, pw, args, opti, dumpCheckin, dumpPackage);
+ mBroadcastController.dumpBroadcastStatsCheckinLocked(fd, pw, args, opti, dumpCheckin,
+ dumpPackage);
} else {
if (dumpClient) {
// dumpEverything() will take the lock when needed, and momentarily drop
@@ -11783,42 +11645,6 @@
}
}
- void writeBroadcastsToProtoLocked(ProtoOutputStream proto) {
- if (mRegisteredReceivers.size() > 0) {
- Iterator it = mRegisteredReceivers.values().iterator();
- while (it.hasNext()) {
- ReceiverList r = (ReceiverList)it.next();
- r.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_LIST);
- }
- }
- mReceiverResolver.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_RESOLVER);
- mBroadcastQueue.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
- synchronized (mStickyBroadcasts) {
- for (int user = 0; user < mStickyBroadcasts.size(); user++) {
- long token = proto.start(
- ActivityManagerServiceDumpBroadcastsProto.STICKY_BROADCASTS);
- proto.write(StickyBroadcastProto.USER, mStickyBroadcasts.keyAt(user));
- for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
- : mStickyBroadcasts.valueAt(user).entrySet()) {
- long actionToken = proto.start(StickyBroadcastProto.ACTIONS);
- proto.write(StickyBroadcastProto.StickyAction.NAME, ent.getKey());
- for (StickyBroadcast broadcast : ent.getValue()) {
- broadcast.intent.dumpDebug(proto, StickyBroadcastProto.StickyAction.INTENTS,
- false, true, true, false);
- }
- proto.end(actionToken);
- }
- proto.end(token);
- }
- }
-
- long handlerToken = proto.start(ActivityManagerServiceDumpBroadcastsProto.HANDLER);
- proto.write(ActivityManagerServiceDumpBroadcastsProto.MainHandler.HANDLER, mHandler.toString());
- mHandler.getLooper().dumpDebug(proto,
- ActivityManagerServiceDumpBroadcastsProto.MainHandler.LOOPER);
- proto.end(handlerToken);
- }
-
void dumpAllowedAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage) {
pw.println(
@@ -11854,219 +11680,6 @@
}
}
- @NeverCompile
- void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- int opti, boolean dumpAll, String dumpPackage) {
- boolean dumpConstants = true;
- boolean dumpHistory = true;
- boolean needSep = false;
- boolean onlyHistory = false;
- boolean printedAnything = false;
- boolean onlyReceivers = false;
- int filteredUid = Process.INVALID_UID;
-
- if ("history".equals(dumpPackage)) {
- if (opti < args.length && "-s".equals(args[opti])) {
- dumpAll = false;
- }
- onlyHistory = true;
- dumpPackage = null;
- }
- if ("receivers".equals(dumpPackage)) {
- onlyReceivers = true;
- dumpPackage = null;
- if (opti + 2 <= args.length) {
- for (int i = opti; i < args.length; i++) {
- String arg = args[i];
- switch (arg) {
- case "--uid":
- filteredUid = getIntArg(pw, args, ++i, Process.INVALID_UID);
- if (filteredUid == Process.INVALID_UID) {
- return;
- }
- break;
- default:
- pw.printf("Invalid argument at index %d: %s\n", i, arg);
- return;
- }
- }
- }
- }
- if (DEBUG_BROADCAST) {
- Slogf.d(TAG_BROADCAST, "dumpBroadcastsLocked(): dumpPackage=%s, onlyHistory=%b, "
- + "onlyReceivers=%b, filteredUid=%d", dumpPackage, onlyHistory, onlyReceivers,
- filteredUid);
- }
-
- pw.println("ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts)");
- if (!onlyHistory && dumpAll) {
- if (mRegisteredReceivers.size() > 0) {
- boolean printed = false;
- Iterator it = mRegisteredReceivers.values().iterator();
- while (it.hasNext()) {
- ReceiverList r = (ReceiverList)it.next();
- if (dumpPackage != null && (r.app == null ||
- !dumpPackage.equals(r.app.info.packageName))) {
- continue;
- }
- if (filteredUid != Process.INVALID_UID && filteredUid != r.app.uid) {
- if (DEBUG_BROADCAST) {
- Slogf.v(TAG_BROADCAST, "dumpBroadcastsLocked(): skipping receiver whose"
- + " uid (%d) is not %d: %s", r.app.uid, filteredUid, r.app);
- }
- continue;
- }
- if (!printed) {
- pw.println(" Registered Receivers:");
- needSep = true;
- printed = true;
- printedAnything = true;
- }
- pw.print(" * "); pw.println(r);
- r.dump(pw, " ");
- }
- } else {
- if (onlyReceivers) {
- pw.println(" (no registered receivers)");
- }
- }
-
- if (!onlyReceivers) {
- if (mReceiverResolver.dump(pw, needSep
- ? "\n Receiver Resolver Table:" : " Receiver Resolver Table:",
- " ", dumpPackage, false, false)) {
- needSep = true;
- printedAnything = true;
- }
- }
- }
-
- if (!onlyReceivers) {
- needSep = mBroadcastQueue.dumpLocked(fd, pw, args, opti,
- dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
- printedAnything |= needSep;
- }
-
- needSep = true;
-
- synchronized (mStickyBroadcasts) {
- if (!onlyHistory && !onlyReceivers && mStickyBroadcasts != null
- && dumpPackage == null) {
- for (int user = 0; user < mStickyBroadcasts.size(); user++) {
- if (needSep) {
- pw.println();
- }
- needSep = true;
- printedAnything = true;
- pw.print(" Sticky broadcasts for user ");
- pw.print(mStickyBroadcasts.keyAt(user));
- pw.println(":");
- StringBuilder sb = new StringBuilder(128);
- for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
- : mStickyBroadcasts.valueAt(user).entrySet()) {
- pw.print(" * Sticky action ");
- pw.print(ent.getKey());
- if (dumpAll) {
- pw.println(":");
- ArrayList<StickyBroadcast> broadcasts = ent.getValue();
- final int N = broadcasts.size();
- for (int i = 0; i < N; i++) {
- final Intent intent = broadcasts.get(i).intent;
- final boolean deferUntilActive = broadcasts.get(i).deferUntilActive;
- sb.setLength(0);
- sb.append(" Intent: ");
- intent.toShortString(sb, false, true, false, false);
- pw.print(sb);
- if (deferUntilActive) {
- pw.print(" [D]");
- }
- pw.println();
- pw.print(" originalCallingUid: ");
- pw.println(broadcasts.get(i).originalCallingUid);
- pw.println();
- Bundle bundle = intent.getExtras();
- if (bundle != null) {
- pw.print(" extras: ");
- pw.println(bundle);
- }
- }
- } else {
- pw.println("");
- }
- }
- }
- }
- }
-
- if (!onlyHistory && !onlyReceivers && dumpAll) {
- pw.println();
- pw.println(" Queue " + mBroadcastQueue.toString() + ": "
- + mBroadcastQueue.describeStateLocked());
- pw.println(" mHandler:");
- mHandler.dump(new PrintWriterPrinter(pw), " ");
- needSep = true;
- printedAnything = true;
- }
-
- if (!printedAnything) {
- pw.println(" (nothing)");
- }
- }
-
- @NeverCompile
- void dumpBroadcastStatsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- int opti, boolean dumpAll, String dumpPackage) {
- if (mCurBroadcastStats == null) {
- return;
- }
-
- pw.println("ACTIVITY MANAGER BROADCAST STATS STATE (dumpsys activity broadcast-stats)");
- final long now = SystemClock.elapsedRealtime();
- if (mLastBroadcastStats != null) {
- pw.print(" Last stats (from ");
- TimeUtils.formatDuration(mLastBroadcastStats.mStartRealtime, now, pw);
- pw.print(" to ");
- TimeUtils.formatDuration(mLastBroadcastStats.mEndRealtime, now, pw);
- pw.print(", ");
- TimeUtils.formatDuration(mLastBroadcastStats.mEndUptime
- - mLastBroadcastStats.mStartUptime, pw);
- pw.println(" uptime):");
- if (!mLastBroadcastStats.dumpStats(pw, " ", dumpPackage)) {
- pw.println(" (nothing)");
- }
- pw.println();
- }
- pw.print(" Current stats (from ");
- TimeUtils.formatDuration(mCurBroadcastStats.mStartRealtime, now, pw);
- pw.print(" to now, ");
- TimeUtils.formatDuration(SystemClock.uptimeMillis()
- - mCurBroadcastStats.mStartUptime, pw);
- pw.println(" uptime):");
- if (!mCurBroadcastStats.dumpStats(pw, " ", dumpPackage)) {
- pw.println(" (nothing)");
- }
- }
-
- @NeverCompile
- void dumpBroadcastStatsCheckinLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- int opti, boolean fullCheckin, String dumpPackage) {
- if (mCurBroadcastStats == null) {
- return;
- }
-
- if (mLastBroadcastStats != null) {
- mLastBroadcastStats.dumpCheckinStats(pw, dumpPackage);
- if (fullCheckin) {
- mLastBroadcastStats = null;
- return;
- }
- }
- mCurBroadcastStats.dumpCheckinStats(pw, dumpPackage);
- if (fullCheckin) {
- mCurBroadcastStats = null;
- }
- }
-
void dumpPermissions(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage) {
@@ -14588,33 +14201,6 @@
// BROADCASTS
// =========================================================
- private boolean isInstantApp(ProcessRecord record, @Nullable String callerPackage, int uid) {
- if (UserHandle.getAppId(uid) < FIRST_APPLICATION_UID) {
- return false;
- }
- // Easy case -- we have the app's ProcessRecord.
- if (record != null) {
- return record.info.isInstantApp();
- }
- // Otherwise check with PackageManager.
- IPackageManager pm = AppGlobals.getPackageManager();
- try {
- if (callerPackage == null) {
- final String[] packageNames = pm.getPackagesForUid(uid);
- if (packageNames == null || packageNames.length == 0) {
- throw new IllegalArgumentException("Unable to determine caller package name");
- }
- // Instant Apps can't use shared uids, so its safe to only check the first package.
- callerPackage = packageNames[0];
- }
- mAppOpsService.checkPackage(uid, callerPackage);
- return pm.isInstantApp(callerPackage, UserHandle.getUserId(uid));
- } catch (RemoteException e) {
- Slog.e(TAG, "Error looking up if " + callerPackage + " is an instant app.", e);
- return true;
- }
- }
-
/**
* @deprecated Use {@link #registerReceiverWithFeature}
*/
@@ -14629,657 +14215,12 @@
public Intent registerReceiverWithFeature(IApplicationThread caller, String callerPackage,
String callerFeatureId, String receiverId, IIntentReceiver receiver,
IntentFilter filter, String permission, int userId, int flags) {
- traceRegistrationBegin(receiverId, receiver, filter, userId);
- try {
- return registerReceiverWithFeatureTraced(caller, callerPackage, callerFeatureId,
- receiverId, receiver, filter, permission, userId, flags);
- } finally {
- traceRegistrationEnd();
- }
- }
-
- private static void traceRegistrationBegin(String receiverId, IIntentReceiver receiver,
- IntentFilter filter, int userId) {
- if (!Flags.traceReceiverRegistration()) {
- return;
- }
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- final StringBuilder sb = new StringBuilder("registerReceiver: ");
- sb.append(Binder.getCallingUid()); sb.append('/');
- sb.append(receiverId == null ? "null" : receiverId); sb.append('/');
- final int actionsCount = filter.safeCountActions();
- if (actionsCount > 0) {
- for (int i = 0; i < actionsCount; ++i) {
- sb.append(filter.getAction(i));
- if (i != actionsCount - 1) sb.append(',');
- }
- } else {
- sb.append("null");
- }
- sb.append('/');
- sb.append('u'); sb.append(userId); sb.append('/');
- sb.append(receiver == null ? "null" : receiver.asBinder());
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, sb.toString());
- }
- }
-
- private static void traceRegistrationEnd() {
- if (!Flags.traceReceiverRegistration()) {
- return;
- }
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- }
- }
-
- private Intent registerReceiverWithFeatureTraced(IApplicationThread caller,
- String callerPackage, String callerFeatureId, String receiverId,
- IIntentReceiver receiver, IntentFilter filter, String permission,
- int userId, int flags) {
- enforceNotIsolatedCaller("registerReceiver");
- ArrayList<StickyBroadcast> stickyBroadcasts = null;
- ProcessRecord callerApp = null;
- final boolean visibleToInstantApps
- = (flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
-
- int callingUid;
- int callingPid;
- boolean instantApp;
- synchronized (mProcLock) {
- callerApp = getRecordForAppLOSP(caller);
- if (callerApp == null) {
- Slog.w(TAG, "registerReceiverWithFeature: no app for " + caller);
- return null;
- }
- if (callerApp.info.uid != SYSTEM_UID
- && !callerApp.getPkgList().containsKey(callerPackage)
- && !"android".equals(callerPackage)) {
- throw new SecurityException("Given caller package " + callerPackage
- + " is not running in process " + callerApp);
- }
- callingUid = callerApp.info.uid;
- callingPid = callerApp.getPid();
-
- instantApp = isInstantApp(callerApp, callerPackage, callingUid);
- }
- userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
- ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
-
- // Warn if system internals are registering for important broadcasts
- // without also using a priority to ensure they process the event
- // before normal apps hear about it
- if (UserHandle.isCore(callingUid)) {
- final int priority = filter.getPriority();
- final boolean systemPriority = (priority >= IntentFilter.SYSTEM_HIGH_PRIORITY)
- || (priority <= IntentFilter.SYSTEM_LOW_PRIORITY);
- if (!systemPriority) {
- final int N = filter.countActions();
- for (int i = 0; i < N; i++) {
- // TODO: expand to additional important broadcasts over time
- final String action = filter.getAction(i);
- if (action.startsWith("android.intent.action.USER_")
- || action.startsWith("android.intent.action.PACKAGE_")
- || action.startsWith("android.intent.action.UID_")
- || action.startsWith("android.intent.action.EXTERNAL_")
- || action.startsWith("android.bluetooth.")
- || action.equals(Intent.ACTION_SHUTDOWN)) {
- if (DEBUG_BROADCAST) {
- Slog.wtf(TAG,
- "System internals registering for " + filter.toLongString()
- + " with app priority; this will race with apps!",
- new Throwable());
- }
-
- // When undefined, assume that system internals need
- // to hear about the event first; they can use
- // SYSTEM_LOW_PRIORITY if they need to hear last
- if (priority == 0) {
- filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- }
- break;
- }
- }
- }
- }
-
- Iterator<String> actions = filter.actionsIterator();
- if (actions == null) {
- ArrayList<String> noAction = new ArrayList<String>(1);
- noAction.add(null);
- actions = noAction.iterator();
- }
- boolean onlyProtectedBroadcasts = true;
-
- // Collect stickies of users and check if broadcast is only registered for protected
- // broadcasts
- int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
- synchronized (mStickyBroadcasts) {
- while (actions.hasNext()) {
- String action = actions.next();
- for (int id : userIds) {
- ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
- mStickyBroadcasts.get(id);
- if (stickies != null) {
- ArrayList<StickyBroadcast> broadcasts = stickies.get(action);
- if (broadcasts != null) {
- if (stickyBroadcasts == null) {
- stickyBroadcasts = new ArrayList<>();
- }
- stickyBroadcasts.addAll(broadcasts);
- }
- }
- }
- if (onlyProtectedBroadcasts) {
- try {
- onlyProtectedBroadcasts &=
- AppGlobals.getPackageManager().isProtectedBroadcast(action);
- } catch (RemoteException e) {
- onlyProtectedBroadcasts = false;
- Slog.w(TAG, "Remote exception", e);
- }
- }
- }
- }
-
- if (Process.isSdkSandboxUid(Binder.getCallingUid())) {
- SdkSandboxManagerLocal sdkSandboxManagerLocal =
- LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
- if (sdkSandboxManagerLocal == null) {
- throw new IllegalStateException("SdkSandboxManagerLocal not found when checking"
- + " whether SDK sandbox uid can register to broadcast receivers.");
- }
- if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver(
- /*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) {
- throw new SecurityException("SDK sandbox not allowed to register receiver"
- + " with the given IntentFilter: " + filter.toLongString());
- }
- }
-
- // If the change is enabled, but neither exported or not exported is set, we need to log
- // an error so the consumer can know to explicitly set the value for their flag.
- // If the caller is registering for a sticky broadcast with a null receiver, we won't
- // require a flag
- final boolean explicitExportStateDefined =
- (flags & (Context.RECEIVER_EXPORTED | Context.RECEIVER_NOT_EXPORTED)) != 0;
- if (((flags & Context.RECEIVER_EXPORTED) != 0) && (
- (flags & Context.RECEIVER_NOT_EXPORTED) != 0)) {
- throw new IllegalArgumentException(
- "Receiver can't specify both RECEIVER_EXPORTED and RECEIVER_NOT_EXPORTED"
- + "flag");
- }
-
- // Don't enforce the flag check if we're EITHER registering for only protected
- // broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
- // not be used generally, so we will be marking them as exported by default
- boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
- DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid);
-
- // A receiver that is visible to instant apps must also be exported.
- final boolean unexportedReceiverVisibleToInstantApps =
- ((flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0) && (
- (flags & Context.RECEIVER_NOT_EXPORTED) != 0);
- if (unexportedReceiverVisibleToInstantApps && requireExplicitFlagForDynamicReceivers) {
- throw new IllegalArgumentException(
- "Receiver can't specify both RECEIVER_VISIBLE_TO_INSTANT_APPS and "
- + "RECEIVER_NOT_EXPORTED flag");
- }
-
- if (!onlyProtectedBroadcasts) {
- if (receiver == null && !explicitExportStateDefined) {
- // sticky broadcast, no flag specified (flag isn't required)
- flags |= Context.RECEIVER_EXPORTED;
- } else if (requireExplicitFlagForDynamicReceivers && !explicitExportStateDefined) {
- throw new SecurityException(
- callerPackage + ": One of RECEIVER_EXPORTED or "
- + "RECEIVER_NOT_EXPORTED should be specified when a receiver "
- + "isn't being registered exclusively for system broadcasts");
- // Assume default behavior-- flag check is not enforced
- } else if (!requireExplicitFlagForDynamicReceivers && (
- (flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
- // Change is not enabled, assume exported unless otherwise specified.
- flags |= Context.RECEIVER_EXPORTED;
- }
- } else if ((flags & Context.RECEIVER_NOT_EXPORTED) == 0) {
- flags |= Context.RECEIVER_EXPORTED;
- }
-
- // Dynamic receivers are exported by default for versions prior to T
- final boolean exported = (flags & Context.RECEIVER_EXPORTED) != 0;
-
- ArrayList<StickyBroadcast> allSticky = null;
- if (stickyBroadcasts != null) {
- final ContentResolver resolver = mContext.getContentResolver();
- // Look for any matching sticky broadcasts...
- for (int i = 0, N = stickyBroadcasts.size(); i < N; i++) {
- final StickyBroadcast broadcast = stickyBroadcasts.get(i);
- Intent intent = broadcast.intent;
- // Don't provided intents that aren't available to instant apps.
- if (instantApp &&
- (intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) == 0) {
- continue;
- }
- // If intent has scheme "content", it will need to access
- // provider that needs to lock mProviderMap in ActivityThread
- // and also it may need to wait application response, so we
- // cannot lock ActivityManagerService here.
- final int match;
- if (Flags.avoidResolvingType()) {
- match = filter.match(intent.getAction(), broadcast.resolvedDataType,
- intent.getScheme(), intent.getData(), intent.getCategories(),
- TAG, false /* supportsWildcards */, null /* ignoreActions */,
- intent.getExtras());
- } else {
- match = filter.match(resolver, intent, true, TAG);
- }
- if (match >= 0) {
- if (allSticky == null) {
- allSticky = new ArrayList<>();
- }
- allSticky.add(broadcast);
- }
- }
- }
-
- // The first sticky in the list is returned directly back to the client.
- Intent sticky = allSticky != null ? allSticky.get(0).intent : null;
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);
- if (receiver == null) {
- return sticky;
- }
-
- // SafetyNet logging for b/177931370. If any process other than system_server tries to
- // listen to this broadcast action, then log it.
- if (callingPid != Process.myPid()) {
- if (filter.hasAction("com.android.server.net.action.SNOOZE_WARNING")
- || filter.hasAction("com.android.server.net.action.SNOOZE_RAPID")) {
- EventLog.writeEvent(0x534e4554, "177931370", callingUid, "");
- }
- }
-
- synchronized (this) {
- IApplicationThread thread;
- if (callerApp != null && ((thread = callerApp.getThread()) == null
- || thread.asBinder() != caller.asBinder())) {
- // Original caller already died
- return null;
- }
- ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
- if (rl == null) {
- rl = new ReceiverList(this, callerApp, callingPid, callingUid,
- userId, receiver);
- if (rl.app != null) {
- final int totalReceiversForApp = rl.app.mReceivers.numberOfReceivers();
- if (totalReceiversForApp >= MAX_RECEIVERS_ALLOWED_PER_APP) {
- throw new IllegalStateException("Too many receivers, total of "
- + totalReceiversForApp + ", registered for pid: "
- + rl.pid + ", callerPackage: " + callerPackage);
- }
- rl.app.mReceivers.addReceiver(rl);
- } else {
- try {
- receiver.asBinder().linkToDeath(rl, 0);
- } catch (RemoteException e) {
- return sticky;
- }
- rl.linkedToDeath = true;
- }
- mRegisteredReceivers.put(receiver.asBinder(), rl);
- } else if (rl.uid != callingUid) {
- throw new IllegalArgumentException(
- "Receiver requested to register for uid " + callingUid
- + " was previously registered for uid " + rl.uid
- + " callerPackage is " + callerPackage);
- } else if (rl.pid != callingPid) {
- throw new IllegalArgumentException(
- "Receiver requested to register for pid " + callingPid
- + " was previously registered for pid " + rl.pid
- + " callerPackage is " + callerPackage);
- } else if (rl.userId != userId) {
- throw new IllegalArgumentException(
- "Receiver requested to register for user " + userId
- + " was previously registered for user " + rl.userId
- + " callerPackage is " + callerPackage);
- }
- BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
- receiverId, permission, callingUid, userId, instantApp, visibleToInstantApps,
- exported);
- if (rl.containsFilter(filter)) {
- Slog.w(TAG, "Receiver with filter " + filter
- + " already registered for pid " + rl.pid
- + ", callerPackage is " + callerPackage);
- } else {
- rl.add(bf);
- if (!bf.debugCheck()) {
- Slog.w(TAG, "==> For Dynamic broadcast");
- }
- mReceiverResolver.addFilter(getPackageManagerInternal().snapshot(), bf);
- }
-
- // Enqueue broadcasts for all existing stickies that match
- // this filter.
- if (allSticky != null) {
- ArrayList receivers = new ArrayList();
- receivers.add(bf);
- sticky = null;
-
- final int stickyCount = allSticky.size();
- for (int i = 0; i < stickyCount; i++) {
- final StickyBroadcast broadcast = allSticky.get(i);
- final int originalStickyCallingUid = allSticky.get(i).originalCallingUid;
- // TODO(b/281889567): consider using checkComponentPermission instead of
- // canAccessUnexportedComponents
- if (sticky == null && (exported || originalStickyCallingUid == callingUid
- || ActivityManager.canAccessUnexportedComponents(
- originalStickyCallingUid))) {
- sticky = broadcast.intent;
- }
- BroadcastQueue queue = mBroadcastQueue;
- BroadcastRecord r = new BroadcastRecord(queue, broadcast.intent, null,
- null, null, -1, -1, false, null, null, null, null, OP_NONE,
- BroadcastOptions.makeWithDeferUntilActive(broadcast.deferUntilActive),
- receivers, null, null, 0, null, null, false, true, true, -1,
- originalStickyCallingUid, BackgroundStartPrivileges.NONE,
- false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
- null /* filterExtrasForReceiver */,
- broadcast.originalCallingAppProcessState);
- queue.enqueueBroadcastLocked(r);
- }
- }
-
- return sticky;
- }
+ return mBroadcastController.registerReceiverWithFeature(caller, callerPackage,
+ callerFeatureId, receiverId, receiver, filter, permission, userId, flags);
}
public void unregisterReceiver(IIntentReceiver receiver) {
- traceUnregistrationBegin(receiver);
- try {
- unregisterReceiverTraced(receiver);
- } finally {
- traceUnregistrationEnd();
- }
- }
-
- private static void traceUnregistrationBegin(IIntentReceiver receiver) {
- if (!Flags.traceReceiverRegistration()) {
- return;
- }
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- TextUtils.formatSimple("unregisterReceiver: %d/%s", Binder.getCallingUid(),
- receiver == null ? "null" : receiver.asBinder()));
- }
- }
-
- private static void traceUnregistrationEnd() {
- if (!Flags.traceReceiverRegistration()) {
- return;
- }
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- }
- }
-
- private void unregisterReceiverTraced(IIntentReceiver receiver) {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Unregister receiver: " + receiver);
-
- final long origId = Binder.clearCallingIdentity();
- try {
- boolean doTrim = false;
- synchronized(this) {
- ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
- if (rl != null) {
- final BroadcastRecord r = rl.curBroadcast;
- if (r != null) {
- final boolean doNext = r.queue.finishReceiverLocked(
- rl.app, r.resultCode, r.resultData, r.resultExtras,
- r.resultAbort, false);
- if (doNext) {
- doTrim = true;
- }
- }
- if (rl.app != null) {
- rl.app.mReceivers.removeReceiver(rl);
- }
- removeReceiverLocked(rl);
- if (rl.linkedToDeath) {
- rl.linkedToDeath = false;
- rl.receiver.asBinder().unlinkToDeath(rl, 0);
- }
- }
-
- // If we actually concluded any broadcasts, we might now be able
- // to trim the recipients' apps from our working set
- if (doTrim) {
- trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
- return;
- }
- }
-
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
-
- void removeReceiverLocked(ReceiverList rl) {
- mRegisteredReceivers.remove(rl.receiver.asBinder());
- for (int i = rl.size() - 1; i >= 0; i--) {
- mReceiverResolver.removeFilter(rl.get(i));
- }
- }
-
- private final void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
- mProcessList.sendPackageBroadcastLocked(cmd, packages, userId);
- }
-
- private List<ResolveInfo> collectReceiverComponents(
- Intent intent, String resolvedType, int callingUid, int callingPid,
- int[] users, int[] broadcastAllowList) {
- // TODO: come back and remove this assumption to triage all broadcasts
- long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;
-
- List<ResolveInfo> receivers = null;
- HashSet<ComponentName> singleUserReceivers = null;
- boolean scannedFirstReceivers = false;
- for (int user : users) {
- // Skip users that have Shell restrictions
- if (callingUid == SHELL_UID
- && mUserController.hasUserRestriction(
- UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
- continue;
- }
- List<ResolveInfo> newReceivers = mPackageManagerInt.queryIntentReceivers(
- intent, resolvedType, pmFlags, callingUid, callingPid, user, /* forSend */true);
- if (user != UserHandle.USER_SYSTEM && newReceivers != null) {
- // If this is not the system user, we need to check for
- // any receivers that should be filtered out.
- for (int i = 0; i < newReceivers.size(); i++) {
- ResolveInfo ri = newReceivers.get(i);
- if ((ri.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
- newReceivers.remove(i);
- i--;
- }
- }
- }
- // Replace the alias receivers with their targets.
- if (newReceivers != null) {
- for (int i = newReceivers.size() - 1; i >= 0; i--) {
- final ResolveInfo ri = newReceivers.get(i);
- final Resolution<ResolveInfo> resolution =
- mComponentAliasResolver.resolveReceiver(intent, ri, resolvedType,
- pmFlags, user, callingUid, callingPid);
- if (resolution == null) {
- // It was an alias, but the target was not found.
- newReceivers.remove(i);
- continue;
- }
- if (resolution.isAlias()) {
- newReceivers.set(i, resolution.getTarget());
- }
- }
- }
- if (newReceivers != null && newReceivers.size() == 0) {
- newReceivers = null;
- }
-
- if (receivers == null) {
- receivers = newReceivers;
- } else if (newReceivers != null) {
- // We need to concatenate the additional receivers
- // found with what we have do far. This would be easy,
- // but we also need to de-dup any receivers that are
- // singleUser.
- if (!scannedFirstReceivers) {
- // Collect any single user receivers we had already retrieved.
- scannedFirstReceivers = true;
- for (int i = 0; i < receivers.size(); i++) {
- ResolveInfo ri = receivers.get(i);
- if ((ri.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
- ComponentName cn = new ComponentName(
- ri.activityInfo.packageName, ri.activityInfo.name);
- if (singleUserReceivers == null) {
- singleUserReceivers = new HashSet<ComponentName>();
- }
- singleUserReceivers.add(cn);
- }
- }
- }
- // Add the new results to the existing results, tracking
- // and de-dupping single user receivers.
- for (int i = 0; i < newReceivers.size(); i++) {
- ResolveInfo ri = newReceivers.get(i);
- if ((ri.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
- ComponentName cn = new ComponentName(
- ri.activityInfo.packageName, ri.activityInfo.name);
- if (singleUserReceivers == null) {
- singleUserReceivers = new HashSet<ComponentName>();
- }
- if (!singleUserReceivers.contains(cn)) {
- singleUserReceivers.add(cn);
- receivers.add(ri);
- }
- } else {
- receivers.add(ri);
- }
- }
- }
- }
- if (receivers != null && broadcastAllowList != null) {
- for (int i = receivers.size() - 1; i >= 0; i--) {
- final int receiverAppId = UserHandle.getAppId(
- receivers.get(i).activityInfo.applicationInfo.uid);
- if (receiverAppId >= Process.FIRST_APPLICATION_UID
- && Arrays.binarySearch(broadcastAllowList, receiverAppId) < 0) {
- receivers.remove(i);
- }
- }
- }
- return receivers;
- }
-
- private void checkBroadcastFromSystem(Intent intent, ProcessRecord callerApp,
- String callerPackage, int callingUid, boolean isProtectedBroadcast, List receivers) {
- if ((intent.getFlags() & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
- // Don't yell about broadcasts sent via shell
- return;
- }
-
- final String action = intent.getAction();
- if (isProtectedBroadcast
- || Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
- || Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(action)
- || Intent.ACTION_MEDIA_BUTTON.equals(action)
- || Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action)
- || Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(action)
- || Intent.ACTION_MASTER_CLEAR.equals(action)
- || Intent.ACTION_FACTORY_RESET.equals(action)
- || AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
- || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)
- || TelephonyManager.ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE.equals(action)
- || SuggestionSpan.ACTION_SUGGESTION_PICKED.equals(action)
- || AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION.equals(action)
- || AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION.equals(action)) {
- // Broadcast is either protected, or it's a public action that
- // we've relaxed, so it's fine for system internals to send.
- return;
- }
-
- // This broadcast may be a problem... but there are often system components that
- // want to send an internal broadcast to themselves, which is annoying to have to
- // explicitly list each action as a protected broadcast, so we will check for that
- // one safe case and allow it: an explicit broadcast, only being received by something
- // that has protected itself.
- if (intent.getPackage() != null || intent.getComponent() != null) {
- if (receivers == null || receivers.size() == 0) {
- // Intent is explicit and there's no receivers.
- // This happens, e.g. , when a system component sends a broadcast to
- // its own runtime receiver, and there's no manifest receivers for it,
- // because this method is called twice for each broadcast,
- // for runtime receivers and manifest receivers and the later check would find
- // no receivers.
- return;
- }
- boolean allProtected = true;
- for (int i = receivers.size()-1; i >= 0; i--) {
- Object target = receivers.get(i);
- if (target instanceof ResolveInfo) {
- ResolveInfo ri = (ResolveInfo)target;
- if (ri.activityInfo.exported && ri.activityInfo.permission == null) {
- allProtected = false;
- break;
- }
- } else {
- BroadcastFilter bf = (BroadcastFilter)target;
- if (bf.exported && bf.requiredPermission == null) {
- allProtected = false;
- break;
- }
- }
- }
- if (allProtected) {
- // All safe!
- return;
- }
- }
-
- // The vast majority of broadcasts sent from system internals
- // should be protected to avoid security holes, so yell loudly
- // to ensure we examine these cases.
- if (callerApp != null) {
- Log.wtf(TAG, "Sending non-protected broadcast " + action
- + " from system " + callerApp.toShortString() + " pkg " + callerPackage,
- new Throwable());
- } else {
- Log.wtf(TAG, "Sending non-protected broadcast " + action
- + " from system uid " + UserHandle.formatUid(callingUid)
- + " pkg " + callerPackage,
- new Throwable());
- }
- }
-
- // Apply permission policy around the use of specific broadcast options
- void enforceBroadcastOptionPermissionsInternal(
- @Nullable Bundle options, int callingUid) {
- enforceBroadcastOptionPermissionsInternal(BroadcastOptions.fromBundleNullable(options),
- callingUid);
- }
-
- void enforceBroadcastOptionPermissionsInternal(
- @Nullable BroadcastOptions options, int callingUid) {
- if (options != null && callingUid != Process.SYSTEM_UID) {
- if (options.isAlarmBroadcast()) {
- if (DEBUG_BROADCAST_LIGHT) {
- Slog.w(TAG, "Non-system caller " + callingUid
- + " may not flag broadcast as alarm");
- }
- throw new SecurityException(
- "Non-system callers may not flag broadcasts as alarm");
- }
- if (options.isInteractive()) {
- enforceCallingPermission(
- android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE,
- "setInteractive");
- }
- }
+ mBroadcastController.unregisterReceiver(receiver);
}
@GuardedBy("this")
@@ -15290,1033 +14231,14 @@
String[] excludedPackages, int appOp, Bundle bOptions, boolean ordered,
boolean sticky, int callingPid,
int callingUid, int realCallingUid, int realCallingPid, int userId) {
- return broadcastIntentLocked(callerApp, callerPackage, callerFeatureId, intent,
- resolvedType, null, resultTo, resultCode, resultData, resultExtras,
+ return mBroadcastController.broadcastIntentLocked(callerApp, callerPackage, callerFeatureId,
+ intent, resolvedType, null, resultTo, resultCode, resultData, resultExtras,
requiredPermissions, excludedPermissions, excludedPackages, appOp, bOptions,
ordered, sticky, callingPid, callingUid, realCallingUid, realCallingPid, userId,
BackgroundStartPrivileges.NONE,
null /* broadcastAllowList */, null /* filterExtrasForReceiver */);
}
- @GuardedBy("this")
- final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage,
- @Nullable String callerFeatureId, Intent intent, String resolvedType,
- ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
- Bundle resultExtras, String[] requiredPermissions,
- String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
- boolean ordered, boolean sticky, int callingPid, int callingUid,
- int realCallingUid, int realCallingPid, int userId,
- BackgroundStartPrivileges backgroundStartPrivileges,
- @Nullable int[] broadcastAllowList,
- @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
- final int cookie = traceBroadcastIntentBegin(intent, resultTo, ordered, sticky,
- callingUid, realCallingUid, userId);
- try {
- final BroadcastSentEventRecord broadcastSentEventRecord =
- new BroadcastSentEventRecord();
- final int res = broadcastIntentLockedTraced(callerApp, callerPackage, callerFeatureId,
- intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
- resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
- appOp, BroadcastOptions.fromBundleNullable(bOptions), ordered, sticky,
- callingPid, callingUid, realCallingUid, realCallingPid, userId,
- backgroundStartPrivileges, broadcastAllowList, filterExtrasForReceiver,
- broadcastSentEventRecord);
- broadcastSentEventRecord.setResult(res);
- broadcastSentEventRecord.logToStatsd();
- return res;
- } finally {
- traceBroadcastIntentEnd(cookie);
- }
- }
-
- private static int traceBroadcastIntentBegin(Intent intent, IIntentReceiver resultTo,
- boolean ordered, boolean sticky, int callingUid, int realCallingUid, int userId) {
- if (!Flags.traceReceiverRegistration()) {
- return BroadcastQueue.traceBegin("broadcastIntentLockedTraced");
- }
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- final StringBuilder sb = new StringBuilder("broadcastIntent: ");
- sb.append(callingUid); sb.append('/');
- final String action = intent.getAction();
- sb.append(action == null ? null : action); sb.append('/');
- sb.append("0x"); sb.append(Integer.toHexString(intent.getFlags())); sb.append('/');
- sb.append(ordered ? "O" : "_");
- sb.append(sticky ? "S" : "_");
- sb.append(resultTo != null ? "C" : "_");
- sb.append('/');
- sb.append('u'); sb.append(userId);
- if (callingUid != realCallingUid) {
- sb.append('/');
- sb.append("sender="); sb.append(realCallingUid);
- }
- return BroadcastQueue.traceBegin(sb.toString());
- }
- return 0;
- }
-
- private static void traceBroadcastIntentEnd(int cookie) {
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- BroadcastQueue.traceEnd(cookie);
- }
- }
-
- @GuardedBy("this")
- final int broadcastIntentLockedTraced(ProcessRecord callerApp, String callerPackage,
- @Nullable String callerFeatureId, Intent intent, String resolvedType,
- ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
- Bundle resultExtras, String[] requiredPermissions,
- String[] excludedPermissions, String[] excludedPackages, int appOp,
- BroadcastOptions brOptions, boolean ordered, boolean sticky, int callingPid,
- int callingUid, int realCallingUid, int realCallingPid, int userId,
- BackgroundStartPrivileges backgroundStartPrivileges,
- @Nullable int[] broadcastAllowList,
- @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
- @NonNull BroadcastSentEventRecord broadcastSentEventRecord) {
- // Ensure all internal loopers are registered for idle checks
- BroadcastLoopers.addMyLooper();
-
- if (Process.isSdkSandboxUid(realCallingUid)) {
- final SdkSandboxManagerLocal sdkSandboxManagerLocal = LocalManagerRegistry.getManager(
- SdkSandboxManagerLocal.class);
- if (sdkSandboxManagerLocal == null) {
- throw new IllegalStateException("SdkSandboxManagerLocal not found when sending"
- + " a broadcast from an SDK sandbox uid.");
- }
- if (!sdkSandboxManagerLocal.canSendBroadcast(intent)) {
- throw new SecurityException(
- "Intent " + intent.getAction() + " may not be broadcast from an SDK sandbox"
- + " uid. Given caller package " + callerPackage + " (pid=" + callingPid
- + ", realCallingUid=" + realCallingUid + ", callingUid= " + callingUid
- + ")");
- }
- }
-
- if ((resultTo != null) && (resultToApp == null)) {
- if (resultTo.asBinder() instanceof BinderProxy) {
- // Warn when requesting results without a way to deliver them
- Slog.wtf(TAG, "Sending broadcast " + intent.getAction()
- + " with resultTo requires resultToApp", new Throwable());
- } else {
- // If not a BinderProxy above, then resultTo is an in-process
- // receiver, so splice in system_server process
- resultToApp = getProcessRecordLocked("system", SYSTEM_UID);
- }
- }
-
- intent = new Intent(intent);
- broadcastSentEventRecord.setIntent(intent);
- broadcastSentEventRecord.setOriginalIntentFlags(intent.getFlags());
- broadcastSentEventRecord.setSenderUid(callingUid);
- broadcastSentEventRecord.setRealSenderUid(realCallingUid);
- broadcastSentEventRecord.setSticky(sticky);
- broadcastSentEventRecord.setOrdered(ordered);
- broadcastSentEventRecord.setResultRequested(resultTo != null);
- final int callerAppProcessState = getRealProcessStateLocked(callerApp, realCallingPid);
- broadcastSentEventRecord.setSenderProcState(callerAppProcessState);
- broadcastSentEventRecord.setSenderUidState(getRealUidStateLocked(callerApp,
- realCallingPid));
-
- final boolean callerInstantApp = isInstantApp(callerApp, callerPackage, callingUid);
- // Instant Apps cannot use FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS
- if (callerInstantApp) {
- intent.setFlags(intent.getFlags() & ~Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
- }
-
- if (userId == UserHandle.USER_ALL && broadcastAllowList != null) {
- Slog.e(TAG, "broadcastAllowList only applies when sending to individual users. "
- + "Assuming restrictive whitelist.");
- broadcastAllowList = new int[]{};
- }
-
- // By default broadcasts do not go to stopped apps.
- intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
-
- // If we have not finished booting, don't allow this to launch new processes.
- if (!mProcessesReady && (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- }
-
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
- (sticky ? "Broadcast sticky: ": "Broadcast: ") + intent
- + " ordered=" + ordered + " userid=" + userId
- + " options=" + (brOptions == null ? "null" : brOptions.toBundle()));
- if ((resultTo != null) && !ordered) {
- if (!UserHandle.isCore(callingUid)) {
- String msg = "Unauthorized unordered resultTo broadcast "
- + intent + " sent from uid " + callingUid;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
- }
-
- userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
- ALLOW_NON_FULL, "broadcast", callerPackage);
-
- // Make sure that the user who is receiving this broadcast or its parent is running.
- // If not, we will just skip it. Make an exception for shutdown broadcasts, upgrade steps.
- if (userId != UserHandle.USER_ALL && !mUserController.isUserOrItsParentRunning(userId)) {
- if ((callingUid != SYSTEM_UID
- || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
- && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
- Slog.w(TAG, "Skipping broadcast of " + intent
- + ": user " + userId + " and its parent (if any) are stopped");
- scheduleCanceledResultTo(resultToApp, resultTo, intent, userId,
- brOptions, callingUid, callerPackage);
- return ActivityManager.BROADCAST_FAILED_USER_STOPPED;
- }
- }
-
- final String action = intent.getAction();
- if (brOptions != null) {
- if (brOptions.getTemporaryAppAllowlistDuration() > 0) {
- // See if the caller is allowed to do this. Note we are checking against
- // the actual real caller (not whoever provided the operation as say a
- // PendingIntent), because that who is actually supplied the arguments.
- if (checkComponentPermission(CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
- realCallingPid, realCallingUid, -1, true)
- != PackageManager.PERMISSION_GRANTED
- && checkComponentPermission(START_ACTIVITIES_FROM_BACKGROUND,
- realCallingPid, realCallingUid, -1, true)
- != PackageManager.PERMISSION_GRANTED
- && checkComponentPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND,
- realCallingPid, realCallingUid, -1, true)
- != PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: " + intent.getAction()
- + " broadcast from " + callerPackage + " (pid=" + callingPid
- + ", uid=" + callingUid + ")"
- + " requires "
- + CHANGE_DEVICE_IDLE_TEMP_WHITELIST + " or "
- + START_ACTIVITIES_FROM_BACKGROUND + " or "
- + START_FOREGROUND_SERVICES_FROM_BACKGROUND;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
- }
- if (brOptions.isDontSendToRestrictedApps()
- && !isUidActiveLOSP(callingUid)
- && isBackgroundRestrictedNoCheck(callingUid, callerPackage)) {
- Slog.i(TAG, "Not sending broadcast " + action + " - app " + callerPackage
- + " has background restrictions");
- return ActivityManager.START_CANCELED;
- }
- if (brOptions.allowsBackgroundActivityStarts()) {
- // See if the caller is allowed to do this. Note we are checking against
- // the actual real caller (not whoever provided the operation as say a
- // PendingIntent), because that who is actually supplied the arguments.
- if (checkComponentPermission(
- android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
- realCallingPid, realCallingUid, -1, true)
- != PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: " + intent.getAction()
- + " broadcast from " + callerPackage + " (pid=" + callingPid
- + ", uid=" + callingUid + ")"
- + " requires "
- + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- } else {
- // We set the token to null since if it wasn't for it we'd allow anyway here
- backgroundStartPrivileges = BackgroundStartPrivileges.ALLOW_BAL;
- }
- }
-
- if (brOptions.getIdForResponseEvent() > 0) {
- enforcePermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS,
- callingPid, callingUid, "recordResponseEventWhileInBackground");
- }
- }
-
- // Verify that protected broadcasts are only being sent by system code,
- // and that system code is only sending protected broadcasts.
- final boolean isProtectedBroadcast;
- try {
- isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
- } catch (RemoteException e) {
- Slog.w(TAG, "Remote exception", e);
- scheduleCanceledResultTo(resultToApp, resultTo, intent,
- userId, brOptions, callingUid, callerPackage);
- return ActivityManager.BROADCAST_SUCCESS;
- }
-
- final boolean isCallerSystem;
- switch (UserHandle.getAppId(callingUid)) {
- case ROOT_UID:
- case SYSTEM_UID:
- case PHONE_UID:
- case BLUETOOTH_UID:
- case NFC_UID:
- case SE_UID:
- case NETWORK_STACK_UID:
- isCallerSystem = true;
- break;
- default:
- isCallerSystem = (callerApp != null) && callerApp.isPersistent();
- break;
- }
-
- // First line security check before anything else: stop non-system apps from
- // sending protected broadcasts.
- if (!isCallerSystem) {
- if (isProtectedBroadcast) {
- String msg = "Permission Denial: not allowed to send broadcast "
- + action + " from pid="
- + callingPid + ", uid=" + callingUid;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
-
- } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
- || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
- // Special case for compatibility: we don't want apps to send this,
- // but historically it has not been protected and apps may be using it
- // to poke their own app widget. So, instead of making it protected,
- // just limit it to the caller.
- if (callerPackage == null) {
- String msg = "Permission Denial: not allowed to send broadcast "
- + action + " from unknown caller.";
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- } else if (intent.getComponent() != null) {
- // They are good enough to send to an explicit component... verify
- // it is being sent to the calling app.
- if (!intent.getComponent().getPackageName().equals(
- callerPackage)) {
- String msg = "Permission Denial: not allowed to send broadcast "
- + action + " to "
- + intent.getComponent().getPackageName() + " from "
- + callerPackage;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
- } else {
- // Limit broadcast to their own package.
- intent.setPackage(callerPackage);
- }
- }
- }
-
- boolean timeoutExempt = false;
-
- if (action != null) {
- if (getBackgroundLaunchBroadcasts().contains(action)) {
- if (DEBUG_BACKGROUND_CHECK) {
- Slog.i(TAG, "Broadcast action " + action + " forcing include-background");
- }
- intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- }
-
- // TODO: b/329211459 - Remove this after background remote intent is fixed.
- if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
- && getWearRemoteIntentAction().equals(action)) {
- final int callerProcState = callerApp != null
- ? callerApp.getCurProcState()
- : ActivityManager.PROCESS_STATE_NONEXISTENT;
- if (ActivityManager.RunningAppProcessInfo.procStateToImportance(callerProcState)
- > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
- return ActivityManager.START_CANCELED;
- }
- }
-
- switch (action) {
- case Intent.ACTION_MEDIA_SCANNER_SCAN_FILE:
- UserManagerInternal umInternal = LocalServices.getService(
- UserManagerInternal.class);
- UserInfo userInfo = umInternal.getUserInfo(userId);
- if (userInfo != null && userInfo.isCloneProfile()) {
- userId = umInternal.getProfileParentId(userId);
- }
- break;
- case Intent.ACTION_UID_REMOVED:
- case Intent.ACTION_PACKAGE_REMOVED:
- case Intent.ACTION_PACKAGE_CHANGED:
- case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
- case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
- case Intent.ACTION_PACKAGES_SUSPENDED:
- case Intent.ACTION_PACKAGES_UNSUSPENDED:
- // Handle special intents: if this broadcast is from the package
- // manager about a package being removed, we need to remove all of
- // its activities from the history stack.
- if (checkComponentPermission(
- android.Manifest.permission.BROADCAST_PACKAGE_REMOVED,
- callingPid, callingUid, -1, true)
- != PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: " + intent.getAction()
- + " broadcast from " + callerPackage + " (pid=" + callingPid
- + ", uid=" + callingUid + ")"
- + " requires "
- + android.Manifest.permission.BROADCAST_PACKAGE_REMOVED;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
- switch (action) {
- case Intent.ACTION_UID_REMOVED:
- final int uid = getUidFromIntent(intent);
- if (uid >= 0) {
- mBatteryStatsService.removeUid(uid);
- if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
- mAppOpsService.resetAllModes(UserHandle.getUserId(uid),
- intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
- } else {
- mAppOpsService.uidRemoved(uid);
- mServices.onUidRemovedLocked(uid);
- }
- }
- break;
- case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
- // If resources are unavailable just force stop all those packages
- // and flush the attribute cache as well.
- String list[] =
- intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- if (list != null && list.length > 0) {
- for (int i = 0; i < list.length; i++) {
- forceStopPackageLocked(list[i], -1, false, true, true,
- false, false, false, userId, "storage unmount");
- }
- mAtmInternal.cleanupRecentTasksForUser(UserHandle.USER_ALL);
- sendPackageBroadcastLocked(
- ApplicationThreadConstants.EXTERNAL_STORAGE_UNAVAILABLE,
- list, userId);
- }
- break;
- case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
- mAtmInternal.cleanupRecentTasksForUser(UserHandle.USER_ALL);
- break;
- case Intent.ACTION_PACKAGE_REMOVED:
- case Intent.ACTION_PACKAGE_CHANGED:
- Uri data = intent.getData();
- String ssp;
- if (data != null && (ssp=data.getSchemeSpecificPart()) != null) {
- boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals(action);
- final boolean replacing =
- intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
- final boolean killProcess =
- !intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false);
- final boolean fullUninstall = removed && !replacing;
-
- if (removed) {
- if (killProcess) {
- forceStopPackageLocked(ssp, UserHandle.getAppId(
- intent.getIntExtra(Intent.EXTRA_UID, -1)),
- false, true, true, false, fullUninstall, false,
- userId, "pkg removed");
- getPackageManagerInternal()
- .onPackageProcessKilledForUninstall(ssp);
- } else {
- // Kill any app zygotes always, since they can't fork new
- // processes with references to the old code
- forceStopAppZygoteLocked(ssp, UserHandle.getAppId(
- intent.getIntExtra(Intent.EXTRA_UID, -1)),
- userId);
- }
- final int cmd = killProcess
- ? ApplicationThreadConstants.PACKAGE_REMOVED
- : ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL;
- sendPackageBroadcastLocked(cmd,
- new String[] {ssp}, userId);
- if (fullUninstall) {
- // Remove all permissions granted from/to this package
- mUgmInternal.removeUriPermissionsForPackage(ssp, userId,
- true, false);
-
- mAtmInternal.removeRecentTasksByPackageName(ssp, userId);
-
- mServices.forceStopPackageLocked(ssp, userId);
- mAtmInternal.onPackageUninstalled(ssp, userId);
- mBatteryStatsService.notePackageUninstalled(ssp);
- }
- } else {
- if (killProcess) {
- int reason;
- int subReason;
- if (replacing) {
- reason = ApplicationExitInfo.REASON_PACKAGE_UPDATED;
- subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
- } else {
- reason =
- ApplicationExitInfo.REASON_PACKAGE_STATE_CHANGE;
- subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
- }
-
- final int extraUid = intent.getIntExtra(Intent.EXTRA_UID,
- -1);
- synchronized (mProcLock) {
- mProcessList.killPackageProcessesLSP(ssp,
- UserHandle.getAppId(extraUid),
- userId, ProcessList.INVALID_ADJ,
- reason,
- subReason,
- "change " + ssp);
- }
- }
- cleanupDisabledPackageComponentsLocked(ssp, userId,
- intent.getStringArrayExtra(
- Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST));
- mServices.schedulePendingServiceStartLocked(ssp, userId);
- }
- }
- break;
- case Intent.ACTION_PACKAGES_SUSPENDED:
- case Intent.ACTION_PACKAGES_UNSUSPENDED:
- final boolean suspended = Intent.ACTION_PACKAGES_SUSPENDED.equals(
- intent.getAction());
- final String[] packageNames = intent.getStringArrayExtra(
- Intent.EXTRA_CHANGED_PACKAGE_LIST);
- final int userIdExtra = intent.getIntExtra(
- Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-
- mAtmInternal.onPackagesSuspendedChanged(packageNames, suspended,
- userIdExtra);
-
- final boolean quarantined = intent.getBooleanExtra(
- Intent.EXTRA_QUARANTINED, false);
- if (suspended && quarantined && packageNames != null) {
- for (int i = 0; i < packageNames.length; i++) {
- forceStopPackage(packageNames[i], userId,
- ActivityManager.FLAG_OR_STOPPED, "quarantined");
- }
- }
-
- break;
- }
- break;
- case Intent.ACTION_PACKAGE_REPLACED:
- {
- final Uri data = intent.getData();
- final String ssp;
- if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
- ApplicationInfo aInfo = null;
- try {
- aInfo = AppGlobals.getPackageManager()
- .getApplicationInfo(ssp, STOCK_PM_FLAGS, userId);
- } catch (RemoteException ignore) {}
- if (aInfo == null) {
- Slog.w(TAG, "Dropping ACTION_PACKAGE_REPLACED for non-existent pkg:"
- + " ssp=" + ssp + " data=" + data);
- scheduleCanceledResultTo(resultToApp, resultTo, intent,
- userId, brOptions, callingUid, callerPackage);
- return ActivityManager.BROADCAST_SUCCESS;
- }
- updateAssociationForApp(aInfo);
- mAtmInternal.onPackageReplaced(aInfo);
- mServices.updateServiceApplicationInfoLocked(aInfo);
- sendPackageBroadcastLocked(ApplicationThreadConstants.PACKAGE_REPLACED,
- new String[] {ssp}, userId);
- }
- break;
- }
- case Intent.ACTION_PACKAGE_ADDED:
- {
- // Special case for adding a package: by default turn on compatibility mode.
- Uri data = intent.getData();
- String ssp;
- if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
- final boolean replacing =
- intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
- mAtmInternal.onPackageAdded(ssp, replacing);
-
- try {
- ApplicationInfo ai = AppGlobals.getPackageManager().
- getApplicationInfo(ssp, STOCK_PM_FLAGS, 0);
- mBatteryStatsService.notePackageInstalled(ssp,
- ai != null ? ai.longVersionCode : 0);
- } catch (RemoteException e) {
- }
- }
- break;
- }
- case Intent.ACTION_PACKAGE_DATA_CLEARED:
- {
- Uri data = intent.getData();
- String ssp;
- if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
- mAtmInternal.onPackageDataCleared(ssp, userId);
- }
- break;
- }
- case Intent.ACTION_TIMEZONE_CHANGED:
- // If this is the time zone changed action, queue up a message that will reset
- // the timezone of all currently running processes. This message will get
- // queued up before the broadcast happens.
- mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
- break;
- case Intent.ACTION_TIME_CHANGED:
- // EXTRA_TIME_PREF_24_HOUR_FORMAT is optional so we must distinguish between
- // the tri-state value it may contain and "unknown".
- // For convenience we re-use the Intent extra values.
- final int NO_EXTRA_VALUE_FOUND = -1;
- final int timeFormatPreferenceMsgValue = intent.getIntExtra(
- Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT,
- NO_EXTRA_VALUE_FOUND /* defaultValue */);
- // Only send a message if the time preference is available.
- if (timeFormatPreferenceMsgValue != NO_EXTRA_VALUE_FOUND) {
- Message updateTimePreferenceMsg =
- mHandler.obtainMessage(UPDATE_TIME_PREFERENCE_MSG,
- timeFormatPreferenceMsgValue, 0);
- mHandler.sendMessage(updateTimePreferenceMsg);
- }
- mBatteryStatsService.noteCurrentTimeChanged();
- break;
- case ConnectivityManager.ACTION_CLEAR_DNS_CACHE:
- mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);
- break;
- case Proxy.PROXY_CHANGE_ACTION:
- mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG));
- break;
- case android.hardware.Camera.ACTION_NEW_PICTURE:
- case android.hardware.Camera.ACTION_NEW_VIDEO:
- // In N we just turned these off; in O we are turing them back on partly,
- // only for registered receivers. This will still address the main problem
- // (a spam of apps waking up when a picture is taken putting significant
- // memory pressure on the system at a bad point), while still allowing apps
- // that are already actively running to know about this happening.
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- break;
- case android.security.KeyChain.ACTION_TRUST_STORE_CHANGED:
- mHandler.sendEmptyMessage(HANDLE_TRUST_STORAGE_UPDATE_MSG);
- break;
- case "com.android.launcher.action.INSTALL_SHORTCUT":
- // As of O, we no longer support this broadcasts, even for pre-O apps.
- // Apps should now be using ShortcutManager.pinRequestShortcut().
- Log.w(TAG, "Broadcast " + action
- + " no longer supported. It will not be delivered.");
- scheduleCanceledResultTo(resultToApp, resultTo, intent,
- userId, brOptions, callingUid, callerPackage);
- return ActivityManager.BROADCAST_SUCCESS;
- case Intent.ACTION_PRE_BOOT_COMPLETED:
- timeoutExempt = true;
- break;
- case Intent.ACTION_CLOSE_SYSTEM_DIALOGS:
- if (!mAtmInternal.checkCanCloseSystemDialogs(callingPid, callingUid,
- callerPackage)) {
- scheduleCanceledResultTo(resultToApp, resultTo, intent,
- userId, brOptions, callingUid, callerPackage);
- // Returning success seems to be the pattern here
- return ActivityManager.BROADCAST_SUCCESS;
- }
- break;
- }
-
- if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
- Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
- Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
- final int uid = getUidFromIntent(intent);
- if (uid != -1) {
- final UidRecord uidRec = mProcessList.getUidRecordLOSP(uid);
- if (uidRec != null) {
- uidRec.updateHasInternetPermission();
- }
- }
- }
- }
-
- // Add to the sticky list if requested.
- if (sticky) {
- if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,
- callingPid, callingUid)
- != PackageManager.PERMISSION_GRANTED) {
- String msg =
- "Permission Denial: broadcastIntent() requesting a sticky broadcast from"
- + " pid="
- + callingPid
- + ", uid="
- + callingUid
- + " requires "
- + android.Manifest.permission.BROADCAST_STICKY;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
- if (requiredPermissions != null && requiredPermissions.length > 0) {
- Slog.w(TAG, "Can't broadcast sticky intent " + intent
- + " and enforce permissions " + Arrays.toString(requiredPermissions));
- scheduleCanceledResultTo(resultToApp, resultTo, intent,
- userId, brOptions, callingUid, callerPackage);
- return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;
- }
- if (intent.getComponent() != null) {
- throw new SecurityException(
- "Sticky broadcasts can't target a specific component");
- }
- synchronized (mStickyBroadcasts) {
- // We use userId directly here, since the "all" target is maintained
- // as a separate set of sticky broadcasts.
- if (userId != UserHandle.USER_ALL) {
- // But first, if this is not a broadcast to all users, then
- // make sure it doesn't conflict with an existing broadcast to
- // all users.
- ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(
- UserHandle.USER_ALL);
- if (stickies != null) {
- ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
- if (list != null) {
- int N = list.size();
- int i;
- for (i = 0; i < N; i++) {
- if (intent.filterEquals(list.get(i).intent)) {
- throw new IllegalArgumentException("Sticky broadcast " + intent
- + " for user " + userId
- + " conflicts with existing global broadcast");
- }
- }
- }
- }
- }
- ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
- mStickyBroadcasts.get(userId);
- if (stickies == null) {
- stickies = new ArrayMap<>();
- mStickyBroadcasts.put(userId, stickies);
- }
- ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
- if (list == null) {
- list = new ArrayList<>();
- stickies.put(intent.getAction(), list);
- }
- final boolean deferUntilActive = BroadcastRecord.calculateDeferUntilActive(
- callingUid, brOptions, resultTo, ordered,
- BroadcastRecord.calculateUrgent(intent, brOptions));
- final int stickiesCount = list.size();
- int i;
- for (i = 0; i < stickiesCount; i++) {
- if (intent.filterEquals(list.get(i).intent)) {
- // This sticky already exists, replace it.
- list.set(i, StickyBroadcast.create(new Intent(intent), deferUntilActive,
- callingUid, callerAppProcessState, resolvedType));
- break;
- }
- }
- if (i >= stickiesCount) {
- list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
- callingUid, callerAppProcessState, resolvedType));
- }
- }
- }
-
- int[] users;
- if (userId == UserHandle.USER_ALL) {
- // Caller wants broadcast to go to all started users.
- users = mUserController.getStartedUserArray();
- } else {
- // Caller wants broadcast to go to one specific user.
- users = new int[] {userId};
- }
-
- var args = new SaferIntentUtils.IntentArgs(intent, resolvedType,
- true /* isReceiver */, true /* resolveForStart */, callingUid, callingPid);
- args.platformCompat = mPlatformCompat;
-
- // Figure out who all will receive this broadcast.
- final int cookie = BroadcastQueue.traceBegin("queryReceivers");
- List receivers = null;
- List<BroadcastFilter> registeredReceivers = null;
- // Need to resolve the intent to interested receivers...
- if ((intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
- receivers = collectReceiverComponents(
- intent, resolvedType, callingUid, callingPid, users, broadcastAllowList);
- }
- if (intent.getComponent() == null) {
- final PackageDataSnapshot snapshot = getPackageManagerInternal().snapshot();
- if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
- // Query one target user at a time, excluding shell-restricted users
- for (int i = 0; i < users.length; i++) {
- if (mUserController.hasUserRestriction(
- UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {
- continue;
- }
- List<BroadcastFilter> registeredReceiversForUser =
- mReceiverResolver.queryIntent(snapshot, intent,
- resolvedType, false /*defaultOnly*/, users[i]);
- if (registeredReceivers == null) {
- registeredReceivers = registeredReceiversForUser;
- } else if (registeredReceiversForUser != null) {
- registeredReceivers.addAll(registeredReceiversForUser);
- }
- }
- } else {
- registeredReceivers = mReceiverResolver.queryIntent(snapshot, intent,
- resolvedType, false /*defaultOnly*/, userId);
- }
- if (registeredReceivers != null) {
- SaferIntentUtils.blockNullAction(args, registeredReceivers);
- }
- }
- BroadcastQueue.traceEnd(cookie);
-
- final boolean replacePending =
- (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
-
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing broadcast: " + intent.getAction()
- + " replacePending=" + replacePending);
- if (registeredReceivers != null && broadcastAllowList != null) {
- // if a uid whitelist was provided, remove anything in the application space that wasn't
- // in it.
- for (int i = registeredReceivers.size() - 1; i >= 0; i--) {
- final int owningAppId = UserHandle.getAppId(registeredReceivers.get(i).owningUid);
- if (owningAppId >= Process.FIRST_APPLICATION_UID
- && Arrays.binarySearch(broadcastAllowList, owningAppId) < 0) {
- registeredReceivers.remove(i);
- }
- }
- }
-
- int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
-
- // Merge into one list.
- int ir = 0;
- if (receivers != null) {
- // A special case for PACKAGE_ADDED: do not allow the package
- // being added to see this broadcast. This prevents them from
- // using this as a back door to get run as soon as they are
- // installed. Maybe in the future we want to have a special install
- // broadcast or such for apps, but we'd like to deliberately make
- // this decision.
- String skipPackages[] = null;
- if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())
- || Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())
- || Intent.ACTION_PACKAGE_DATA_CLEARED.equals(intent.getAction())) {
- Uri data = intent.getData();
- if (data != null) {
- String pkgName = data.getSchemeSpecificPart();
- if (pkgName != null) {
- skipPackages = new String[] { pkgName };
- }
- }
- } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {
- skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- }
- if (skipPackages != null && (skipPackages.length > 0)) {
- for (String skipPackage : skipPackages) {
- if (skipPackage != null) {
- int NT = receivers.size();
- for (int it=0; it<NT; it++) {
- ResolveInfo curt = (ResolveInfo)receivers.get(it);
- if (curt.activityInfo.packageName.equals(skipPackage)) {
- receivers.remove(it);
- it--;
- NT--;
- }
- }
- }
- }
- }
-
- int NT = receivers != null ? receivers.size() : 0;
- int it = 0;
- ResolveInfo curt = null;
- BroadcastFilter curr = null;
- while (it < NT && ir < NR) {
- if (curt == null) {
- curt = (ResolveInfo)receivers.get(it);
- }
- if (curr == null) {
- curr = registeredReceivers.get(ir);
- }
- if (curr.getPriority() >= curt.priority) {
- // Insert this broadcast record into the final list.
- receivers.add(it, curr);
- ir++;
- curr = null;
- it++;
- NT++;
- } else {
- // Skip to the next ResolveInfo in the final list.
- it++;
- curt = null;
- }
- }
- }
- while (ir < NR) {
- if (receivers == null) {
- receivers = new ArrayList();
- }
- receivers.add(registeredReceivers.get(ir));
- ir++;
- }
-
- if (isCallerSystem) {
- checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
- isProtectedBroadcast, receivers);
- }
-
- if ((receivers != null && receivers.size() > 0)
- || resultTo != null) {
- BroadcastQueue queue = mBroadcastQueue;
- SaferIntentUtils.filterNonExportedComponents(args, receivers);
- BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
- callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
- requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
- receivers, resultToApp, resultTo, resultCode, resultData, resultExtras,
- ordered, sticky, false, userId,
- backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
- callerAppProcessState);
- broadcastSentEventRecord.setBroadcastRecord(r);
-
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
- queue.enqueueBroadcastLocked(r);
- } else {
- // There was nobody interested in the broadcast, but we still want to record
- // that it happened.
- if (intent.getComponent() == null && intent.getPackage() == null
- && (intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
- // This was an implicit broadcast... let's record it for posterity.
- addBroadcastStatLocked(intent.getAction(), callerPackage, 0, 0, 0);
- }
- }
-
- return ActivityManager.BROADCAST_SUCCESS;
- }
-
- @GuardedBy("this")
- private void scheduleCanceledResultTo(ProcessRecord resultToApp, IIntentReceiver resultTo,
- Intent intent, int userId, BroadcastOptions options, int callingUid,
- String callingPackage) {
- if (resultTo == null) {
- return;
- }
- final ProcessRecord app = resultToApp;
- final IApplicationThread thread = (app != null) ? app.getOnewayThread() : null;
- if (thread != null) {
- try {
- final boolean shareIdentity = (options != null && options.isShareIdentityEnabled());
- thread.scheduleRegisteredReceiver(
- resultTo, intent, Activity.RESULT_CANCELED, null, null,
- false, false, true, userId, app.mState.getReportedProcState(),
- shareIdentity ? callingUid : Process.INVALID_UID,
- shareIdentity ? callingPackage : null);
- } catch (RemoteException e) {
- final String msg = "Failed to schedule result of " + intent + " via "
- + app + ": " + e;
- app.killLocked("Can't schedule resultTo", ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
- Slog.d(TAG, msg);
- }
- }
- }
-
- @GuardedBy("this")
- private int getRealProcessStateLocked(ProcessRecord app, int pid) {
- if (app == null) {
- synchronized (mPidsSelfLocked) {
- app = mPidsSelfLocked.get(pid);
- }
- }
- if (app != null && app.getThread() != null && !app.isKilled()) {
- return app.mState.getCurProcState();
- }
- return PROCESS_STATE_NONEXISTENT;
- }
-
- @GuardedBy("this")
- private int getRealUidStateLocked(ProcessRecord app, int pid) {
- if (app == null) {
- synchronized (mPidsSelfLocked) {
- app = mPidsSelfLocked.get(pid);
- }
- }
- if (app != null && app.getThread() != null && !app.isKilled()) {
- final UidRecord uidRecord = app.getUidRecord();
- if (uidRecord != null) {
- return uidRecord.getCurProcState();
- }
- }
- return PROCESS_STATE_NONEXISTENT;
- }
-
- @VisibleForTesting
- ArrayList<StickyBroadcast> getStickyBroadcastsForTest(String action, int userId) {
- synchronized (mStickyBroadcasts) {
- final ArrayMap<String, ArrayList<StickyBroadcast>> stickyBroadcasts =
- mStickyBroadcasts.get(userId);
- if (stickyBroadcasts == null) {
- return null;
- }
- return stickyBroadcasts.get(action);
- }
- }
-
- /**
- * @return uid from the extra field {@link Intent#EXTRA_UID} if present, Otherwise -1
- */
- private int getUidFromIntent(Intent intent) {
- if (intent == null) {
- return -1;
- }
- final Bundle intentExtras = intent.getExtras();
- return intent.hasExtra(Intent.EXTRA_UID)
- ? intentExtras.getInt(Intent.EXTRA_UID) : -1;
- }
-
- final void rotateBroadcastStatsIfNeededLocked() {
- final long now = SystemClock.elapsedRealtime();
- if (mCurBroadcastStats == null ||
- (mCurBroadcastStats.mStartRealtime +(24*60*60*1000) < now)) {
- mLastBroadcastStats = mCurBroadcastStats;
- if (mLastBroadcastStats != null) {
- mLastBroadcastStats.mEndRealtime = SystemClock.elapsedRealtime();
- mLastBroadcastStats.mEndUptime = SystemClock.uptimeMillis();
- }
- mCurBroadcastStats = new BroadcastStats();
- }
- }
-
- final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
- int skipCount, long dispatchTime) {
- rotateBroadcastStatsIfNeededLocked();
- mCurBroadcastStats.addBroadcast(action, srcPackage, receiveCount, skipCount, dispatchTime);
- }
-
- final void addBackgroundCheckViolationLocked(String action, String targetPackage) {
- rotateBroadcastStatsIfNeededLocked();
- mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage);
- }
-
- final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
- final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
- final String callerPackage = info != null ? info.packageName : original.callerPackage;
- if (callerPackage != null) {
- mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
- original.callingUid, 0, callerPackage).sendToTarget();
- }
- }
-
- final Intent verifyBroadcastLocked(Intent intent) {
- if (intent != null) {
- intent.prepareToEnterSystemServer();
- }
-
- int flags = intent.getFlags();
-
- if (!mProcessesReady) {
- // if the caller really truly claims to know what they're doing, go
- // ahead and allow the broadcast without launching any receivers
- if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) != 0) {
- // This will be turned into a FLAG_RECEIVER_REGISTERED_ONLY later on if needed.
- } else if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
- Slog.e(TAG, "Attempt to launch receivers of broadcast intent " + intent
- + " before boot completion");
- throw new IllegalStateException("Cannot broadcast before boot completed");
- }
- }
-
- if ((flags&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
- throw new IllegalArgumentException(
- "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
- }
-
- if ((flags & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
- switch (Binder.getCallingUid()) {
- case ROOT_UID:
- case SHELL_UID:
- break;
- default:
- Slog.w(TAG, "Removing FLAG_RECEIVER_FROM_SHELL because caller is UID "
- + Binder.getCallingUid());
- intent.removeFlags(Intent.FLAG_RECEIVER_FROM_SHELL);
- break;
- }
- }
-
- return intent;
- }
-
/**
* @deprecated Use {@link #broadcastIntentWithFeature}
*/
@@ -16338,110 +14260,14 @@
String[] requiredPermissions, String[] excludedPermissions,
String[] excludedPackages, int appOp, Bundle bOptions,
boolean serialized, boolean sticky, int userId) {
- enforceNotIsolatedCaller("broadcastIntent");
-
- synchronized(this) {
- intent = verifyBroadcastLocked(intent);
-
- final ProcessRecord callerApp = getRecordForAppLOSP(caller);
- final int callingPid = Binder.getCallingPid();
- final int callingUid = Binder.getCallingUid();
-
- // We're delivering the result to the caller
- final ProcessRecord resultToApp = callerApp;
-
- // Permission regimes around sender-supplied broadcast options.
- enforceBroadcastOptionPermissionsInternal(bOptions, callingUid);
-
- final ComponentName cn = intent.getComponent();
-
- Trace.traceBegin(
- Trace.TRACE_TAG_ACTIVITY_MANAGER,
- "broadcastIntent:" + (cn != null ? cn.toString() : intent.getAction()));
-
- final long origId = Binder.clearCallingIdentity();
- try {
- return broadcastIntentLocked(callerApp,
- callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
- intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
- resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
- appOp, bOptions, serialized, sticky, callingPid, callingUid, callingUid,
- callingPid, userId, BackgroundStartPrivileges.NONE, null, null);
- } finally {
- Binder.restoreCallingIdentity(origId);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- }
- }
- }
-
- // Not the binder call surface
- int broadcastIntentInPackage(String packageName, @Nullable String featureId, int uid,
- int realCallingUid, int realCallingPid, Intent intent, String resolvedType,
- ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode,
- String resultData, Bundle resultExtras, String requiredPermission, Bundle bOptions,
- boolean serialized, boolean sticky, int userId,
- BackgroundStartPrivileges backgroundStartPrivileges,
- @Nullable int[] broadcastAllowList) {
- synchronized(this) {
- intent = verifyBroadcastLocked(intent);
-
- final long origId = Binder.clearCallingIdentity();
- String[] requiredPermissions = requiredPermission == null ? null
- : new String[] {requiredPermission};
- try {
- return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
- resultToApp, resultTo, resultCode, resultData, resultExtras,
- requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
- uid, realCallingUid, realCallingPid, userId,
- backgroundStartPrivileges, broadcastAllowList,
- null /* filterExtrasForReceiver */);
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
+ return mBroadcastController.broadcastIntentWithFeature(caller, callingFeatureId, intent,
+ resolvedType, resultTo, resultCode, resultData, resultExtras, requiredPermissions,
+ excludedPermissions, excludedPackages, appOp, bOptions, serialized, sticky, userId);
}
@Override
public final void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) {
- // Refuse possible leaked file descriptors
- if (intent != null && intent.hasFileDescriptors() == true) {
- throw new IllegalArgumentException("File descriptors passed in Intent");
- }
-
- userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
- userId, true, ALLOW_NON_FULL, "removeStickyBroadcast", null);
-
- if (checkCallingPermission(android.Manifest.permission.BROADCAST_STICKY)
- != PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: unbroadcastIntent() from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
- + " requires " + android.Manifest.permission.BROADCAST_STICKY;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
- synchronized (mStickyBroadcasts) {
- ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
- if (stickies != null) {
- ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
- if (list != null) {
- int N = list.size();
- int i;
- for (i = 0; i < N; i++) {
- if (intent.filterEquals(list.get(i).intent)) {
- list.remove(i);
- break;
- }
- }
- if (list.size() <= 0) {
- stickies.remove(intent.getAction());
- }
- }
- if (stickies.size() <= 0) {
- mStickyBroadcasts.remove(userId);
- }
- }
- }
+ mBroadcastController.unbroadcastIntent(caller, intent, userId);
}
void backgroundServicesFinishedLocked(int userId) {
@@ -16450,31 +14276,32 @@
public void finishReceiver(IBinder caller, int resultCode, String resultData,
Bundle resultExtras, boolean resultAbort, int flags) {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Finish receiver: " + caller);
+ mBroadcastController.finishReceiver(caller, resultCode, resultData, resultExtras,
+ resultAbort, flags);
+ }
- // Refuse possible leaked file descriptors
- if (resultExtras != null && resultExtras.hasFileDescriptors()) {
- throw new IllegalArgumentException("File descriptors passed in Bundle");
- }
+ @VisibleForTesting
+ ArrayList<BroadcastController.StickyBroadcast> getStickyBroadcastsForTest(String action,
+ int userId) {
+ return mBroadcastController.getStickyBroadcastsForTest(action, userId);
+ }
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized(this) {
- final ProcessRecord callerApp = getRecordForAppLOSP(caller);
- if (callerApp == null) {
- Slog.w(TAG, "finishReceiver: no app for " + caller);
- return;
- }
+ final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
+ mBroadcastController.notifyBroadcastFinishedLocked(original);
+ }
- mBroadcastQueue.finishReceiverLocked(callerApp, resultCode,
- resultData, resultExtras, resultAbort, true);
- // updateOomAdjLocked() will be done here
- trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
- }
+ final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
+ int skipCount, long dispatchTime) {
+ mBroadcastController.addBroadcastStatLocked(action, srcPackage, receiveCount, skipCount,
+ dispatchTime);
+ }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
+ final void addBackgroundCheckViolationLocked(String action, String targetPackage) {
+ mBroadcastController.addBackgroundCheckViolationLocked(action, targetPackage);
+ }
+
+ void removeReceiverLocked(ReceiverList rl) {
+ mBroadcastController.removeReceiverLocked(rl);
}
// =========================================================
@@ -17839,7 +15666,7 @@
}
@GuardedBy("this")
- private void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
+ void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
// First remove any unused application processes whose package
// has been removed.
boolean didSomething = false;
@@ -18829,7 +16656,7 @@
@Override
public void enforceBroadcastOptionsPermissions(Bundle options, int callingUid) {
- enforceBroadcastOptionPermissionsInternal(options, callingUid);
+ mBroadcastController.enforceBroadcastOptionPermissionsInternal(options, callingUid);
}
/**
@@ -19219,7 +17046,7 @@
@Nullable int[] broadcastAllowList) {
synchronized (ActivityManagerService.this) {
final ProcessRecord resultToApp = getRecordForAppLOSP(resultToThread);
- return ActivityManagerService.this.broadcastIntentInPackage(packageName, featureId,
+ return mBroadcastController.broadcastIntentInPackage(packageName, featureId,
uid, realCallingUid, realCallingPid, intent, resolvedType, resultToApp,
resultTo, resultCode, resultData, resultExtras, requiredPermission,
bOptions, serialized, sticky, userId,
@@ -19236,13 +17063,13 @@
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
@Nullable Bundle bOptions) {
synchronized (ActivityManagerService.this) {
- intent = verifyBroadcastLocked(intent);
+ intent = mBroadcastController.verifyBroadcastLocked(intent);
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
try {
- return ActivityManagerService.this.broadcastIntentLocked(null /*callerApp*/,
+ return mBroadcastController.broadcastIntentLocked(null /*callerApp*/,
null /*callerPackage*/, null /*callingFeatureId*/, intent,
null /* resolvedType */, null /* resultToApp */, resultTo,
0 /* resultCode */, null /* resultData */,
@@ -21159,26 +18986,6 @@
}
}
- /**
- * Gets an {@code int} argument from the given {@code index} on {@code args}, logging an error
- * message on {@code pw} when it cannot be parsed.
- *
- * Returns {@code int} argument or {@code invalidValue} if it could not be parsed.
- */
- private static int getIntArg(PrintWriter pw, String[] args, int index, int invalidValue) {
- if (index > args.length) {
- pw.println("Missing argument");
- return invalidValue;
- }
- String arg = args[index];
- try {
- return Integer.parseInt(arg);
- } catch (Exception e) {
- pw.printf("Non-numeric argument at index %d: %s\n", index, arg);
- return invalidValue;
- }
- }
-
private void notifyMediaProjectionEvent(int uid, @NonNull IBinder projectionToken,
@MediaProjectionTokenEvent int event) {
synchronized (mMediaProjectionTokenMap) {
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
new file mode 100644
index 0000000..32026b2
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -0,0 +1,2410 @@
+/*
+ * Copyright (C) 2024 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.am;
+
+import static android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST;
+import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
+import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.AppOpsManager.OP_NONE;
+import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+import static android.os.Process.BLUETOOTH_UID;
+import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.NETWORK_STACK_UID;
+import static android.os.Process.NFC_UID;
+import static android.os.Process.PHONE_UID;
+import static android.os.Process.ROOT_UID;
+import static android.os.Process.SE_UID;
+import static android.os.Process.SHELL_UID;
+import static android.os.Process.SYSTEM_UID;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
+import static com.android.server.am.ActivityManagerService.CLEAR_DNS_CACHE_MSG;
+import static com.android.server.am.ActivityManagerService.HANDLE_TRUST_STORAGE_UPDATE_MSG;
+import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
+import static com.android.server.am.ActivityManagerService.TAG;
+import static com.android.server.am.ActivityManagerService.UPDATE_HTTP_PROXY_MSG;
+import static com.android.server.am.ActivityManagerService.UPDATE_TIME_PREFERENCE_MSG;
+import static com.android.server.am.ActivityManagerService.UPDATE_TIME_ZONE;
+import static com.android.server.am.ActivityManagerService.checkComponentPermission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.ApplicationExitInfo;
+import android.app.ApplicationThreadConstants;
+import android.app.BackgroundStartPrivileges;
+import android.app.BroadcastOptions;
+import android.app.IApplicationThread;
+import android.app.compat.CompatChanges;
+import android.appwidget.AppWidgetManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.media.audiofx.AudioEffect;
+import android.net.ConnectivityManager;
+import android.net.Proxy;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.BinderProxy;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.text.style.SuggestionSpan;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.IntentResolver;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
+import com.android.server.pm.Computer;
+import com.android.server.pm.SaferIntentUtils;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
+import com.android.server.sdksandbox.SdkSandboxManagerLocal;
+import com.android.server.utils.Slogf;
+
+import dalvik.annotation.optimization.NeverCompile;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+class BroadcastController {
+ private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
+
+ /**
+ * It is now required for apps to explicitly set either
+ * {@link android.content.Context#RECEIVER_EXPORTED} or
+ * {@link android.content.Context#RECEIVER_NOT_EXPORTED} when registering a receiver for an
+ * unprotected broadcast in code.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ private static final long DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED = 161145287L;
+
+ // Maximum number of receivers an app can register.
+ private static final int MAX_RECEIVERS_ALLOWED_PER_APP = 1000;
+
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final ActivityManagerService mService;
+ @NonNull
+ private BroadcastQueue mBroadcastQueue;
+
+ @GuardedBy("mService")
+ BroadcastStats mLastBroadcastStats;
+
+ @GuardedBy("mService")
+ BroadcastStats mCurBroadcastStats;
+
+ /**
+ * Broadcast actions that will always be deliverable to unlaunched/background apps
+ */
+ @GuardedBy("mService")
+ private ArraySet<String> mBackgroundLaunchBroadcasts;
+
+ /**
+ * State of all active sticky broadcasts per user. Keys are the action of the
+ * sticky Intent, values are an ArrayList of all broadcasted intents with
+ * that action (which should usually be one). The SparseArray is keyed
+ * by the user ID the sticky is for, and can include UserHandle.USER_ALL
+ * for stickies that are sent to all users.
+ */
+ @GuardedBy("mStickyBroadcasts")
+ final SparseArray<ArrayMap<String, ArrayList<StickyBroadcast>>> mStickyBroadcasts =
+ new SparseArray<>();
+
+ /**
+ * Keeps track of all IIntentReceivers that have been registered for broadcasts.
+ * Hash keys are the receiver IBinder, hash value is a ReceiverList.
+ */
+ @GuardedBy("mService")
+ final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
+
+ /**
+ * Resolver for broadcast intents to registered receivers.
+ * Holds BroadcastFilter (subclass of IntentFilter).
+ */
+ final IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver =
+ new IntentResolver<>() {
+ @Override
+ protected boolean allowFilterResult(
+ BroadcastFilter filter, List<BroadcastFilter> dest) {
+ IBinder target = filter.receiverList.receiver.asBinder();
+ for (int i = dest.size() - 1; i >= 0; i--) {
+ if (dest.get(i).receiverList.receiver.asBinder() == target) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ protected BroadcastFilter newResult(@NonNull Computer computer, BroadcastFilter filter,
+ int match, int userId, long customFlags) {
+ if (userId == UserHandle.USER_ALL || filter.owningUserId == UserHandle.USER_ALL
+ || userId == filter.owningUserId) {
+ return super.newResult(computer, filter, match, userId, customFlags);
+ }
+ return null;
+ }
+
+ @Override
+ protected IntentFilter getIntentFilter(@NonNull BroadcastFilter input) {
+ return input;
+ }
+
+ @Override
+ protected BroadcastFilter[] newArray(int size) {
+ return new BroadcastFilter[size];
+ }
+
+ @Override
+ protected boolean isPackageForFilter(String packageName, BroadcastFilter filter) {
+ return packageName.equals(filter.packageName);
+ }
+ };
+
+ BroadcastController(Context context, ActivityManagerService service, BroadcastQueue queue) {
+ mContext = context;
+ mService = service;
+ mBroadcastQueue = queue;
+ }
+
+ void setBroadcastQueueForTest(BroadcastQueue broadcastQueue) {
+ mBroadcastQueue = broadcastQueue;
+ }
+
+ Intent registerReceiverWithFeature(IApplicationThread caller, String callerPackage,
+ String callerFeatureId, String receiverId, IIntentReceiver receiver,
+ IntentFilter filter, String permission, int userId, int flags) {
+ traceRegistrationBegin(receiverId, receiver, filter, userId);
+ try {
+ return registerReceiverWithFeatureTraced(caller, callerPackage, callerFeatureId,
+ receiverId, receiver, filter, permission, userId, flags);
+ } finally {
+ traceRegistrationEnd();
+ }
+ }
+
+ private static void traceRegistrationBegin(String receiverId, IIntentReceiver receiver,
+ IntentFilter filter, int userId) {
+ if (!Flags.traceReceiverRegistration()) {
+ return;
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ final StringBuilder sb = new StringBuilder("registerReceiver: ");
+ sb.append(Binder.getCallingUid()); sb.append('/');
+ sb.append(receiverId == null ? "null" : receiverId); sb.append('/');
+ final int actionsCount = filter.safeCountActions();
+ if (actionsCount > 0) {
+ for (int i = 0; i < actionsCount; ++i) {
+ sb.append(filter.getAction(i));
+ if (i != actionsCount - 1) sb.append(',');
+ }
+ } else {
+ sb.append("null");
+ }
+ sb.append('/');
+ sb.append('u'); sb.append(userId); sb.append('/');
+ sb.append(receiver == null ? "null" : receiver.asBinder());
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, sb.toString());
+ }
+ }
+
+ private static void traceRegistrationEnd() {
+ if (!Flags.traceReceiverRegistration()) {
+ return;
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ }
+
+ private Intent registerReceiverWithFeatureTraced(IApplicationThread caller,
+ String callerPackage, String callerFeatureId, String receiverId,
+ IIntentReceiver receiver, IntentFilter filter, String permission,
+ int userId, int flags) {
+ mService.enforceNotIsolatedCaller("registerReceiver");
+ ArrayList<StickyBroadcast> stickyBroadcasts = null;
+ ProcessRecord callerApp = null;
+ final boolean visibleToInstantApps =
+ (flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
+
+ int callingUid;
+ int callingPid;
+ boolean instantApp;
+ synchronized (mService.mProcLock) {
+ callerApp = mService.getRecordForAppLOSP(caller);
+ if (callerApp == null) {
+ Slog.w(TAG, "registerReceiverWithFeature: no app for " + caller);
+ return null;
+ }
+ if (callerApp.info.uid != SYSTEM_UID
+ && !callerApp.getPkgList().containsKey(callerPackage)
+ && !"android".equals(callerPackage)) {
+ throw new SecurityException("Given caller package " + callerPackage
+ + " is not running in process " + callerApp);
+ }
+ callingUid = callerApp.info.uid;
+ callingPid = callerApp.getPid();
+
+ instantApp = isInstantApp(callerApp, callerPackage, callingUid);
+ }
+ userId = mService.mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
+ ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
+
+ // Warn if system internals are registering for important broadcasts
+ // without also using a priority to ensure they process the event
+ // before normal apps hear about it
+ if (UserHandle.isCore(callingUid)) {
+ final int priority = filter.getPriority();
+ final boolean systemPriority = (priority >= IntentFilter.SYSTEM_HIGH_PRIORITY)
+ || (priority <= IntentFilter.SYSTEM_LOW_PRIORITY);
+ if (!systemPriority) {
+ final int N = filter.countActions();
+ for (int i = 0; i < N; i++) {
+ // TODO: expand to additional important broadcasts over time
+ final String action = filter.getAction(i);
+ if (action.startsWith("android.intent.action.USER_")
+ || action.startsWith("android.intent.action.PACKAGE_")
+ || action.startsWith("android.intent.action.UID_")
+ || action.startsWith("android.intent.action.EXTERNAL_")
+ || action.startsWith("android.bluetooth.")
+ || action.equals(Intent.ACTION_SHUTDOWN)) {
+ if (DEBUG_BROADCAST) {
+ Slog.wtf(TAG,
+ "System internals registering for " + filter.toLongString()
+ + " with app priority; this will race with apps!",
+ new Throwable());
+ }
+
+ // When undefined, assume that system internals need
+ // to hear about the event first; they can use
+ // SYSTEM_LOW_PRIORITY if they need to hear last
+ if (priority == 0) {
+ filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ Iterator<String> actions = filter.actionsIterator();
+ if (actions == null) {
+ ArrayList<String> noAction = new ArrayList<String>(1);
+ noAction.add(null);
+ actions = noAction.iterator();
+ }
+ boolean onlyProtectedBroadcasts = true;
+
+ // Collect stickies of users and check if broadcast is only registered for protected
+ // broadcasts
+ int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
+ synchronized (mStickyBroadcasts) {
+ while (actions.hasNext()) {
+ String action = actions.next();
+ for (int id : userIds) {
+ ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
+ mStickyBroadcasts.get(id);
+ if (stickies != null) {
+ ArrayList<StickyBroadcast> broadcasts = stickies.get(action);
+ if (broadcasts != null) {
+ if (stickyBroadcasts == null) {
+ stickyBroadcasts = new ArrayList<>();
+ }
+ stickyBroadcasts.addAll(broadcasts);
+ }
+ }
+ }
+ if (onlyProtectedBroadcasts) {
+ try {
+ onlyProtectedBroadcasts &=
+ AppGlobals.getPackageManager().isProtectedBroadcast(action);
+ } catch (RemoteException e) {
+ onlyProtectedBroadcasts = false;
+ Slog.w(TAG, "Remote exception", e);
+ }
+ }
+ }
+ }
+
+ if (Process.isSdkSandboxUid(Binder.getCallingUid())) {
+ SdkSandboxManagerLocal sdkSandboxManagerLocal =
+ LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
+ if (sdkSandboxManagerLocal == null) {
+ throw new IllegalStateException("SdkSandboxManagerLocal not found when checking"
+ + " whether SDK sandbox uid can register to broadcast receivers.");
+ }
+ if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver(
+ /*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) {
+ throw new SecurityException("SDK sandbox not allowed to register receiver"
+ + " with the given IntentFilter: " + filter.toLongString());
+ }
+ }
+
+ // If the change is enabled, but neither exported or not exported is set, we need to log
+ // an error so the consumer can know to explicitly set the value for their flag.
+ // If the caller is registering for a sticky broadcast with a null receiver, we won't
+ // require a flag
+ final boolean explicitExportStateDefined =
+ (flags & (Context.RECEIVER_EXPORTED | Context.RECEIVER_NOT_EXPORTED)) != 0;
+ if (((flags & Context.RECEIVER_EXPORTED) != 0) && (
+ (flags & Context.RECEIVER_NOT_EXPORTED) != 0)) {
+ throw new IllegalArgumentException(
+ "Receiver can't specify both RECEIVER_EXPORTED and RECEIVER_NOT_EXPORTED"
+ + "flag");
+ }
+
+ // Don't enforce the flag check if we're EITHER registering for only protected
+ // broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
+ // not be used generally, so we will be marking them as exported by default
+ boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
+ DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid);
+
+ // A receiver that is visible to instant apps must also be exported.
+ final boolean unexportedReceiverVisibleToInstantApps =
+ ((flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0) && (
+ (flags & Context.RECEIVER_NOT_EXPORTED) != 0);
+ if (unexportedReceiverVisibleToInstantApps && requireExplicitFlagForDynamicReceivers) {
+ throw new IllegalArgumentException(
+ "Receiver can't specify both RECEIVER_VISIBLE_TO_INSTANT_APPS and "
+ + "RECEIVER_NOT_EXPORTED flag");
+ }
+
+ if (!onlyProtectedBroadcasts) {
+ if (receiver == null && !explicitExportStateDefined) {
+ // sticky broadcast, no flag specified (flag isn't required)
+ flags |= Context.RECEIVER_EXPORTED;
+ } else if (requireExplicitFlagForDynamicReceivers && !explicitExportStateDefined) {
+ throw new SecurityException(
+ callerPackage + ": One of RECEIVER_EXPORTED or "
+ + "RECEIVER_NOT_EXPORTED should be specified when a receiver "
+ + "isn't being registered exclusively for system broadcasts");
+ // Assume default behavior-- flag check is not enforced
+ } else if (!requireExplicitFlagForDynamicReceivers && (
+ (flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
+ // Change is not enabled, assume exported unless otherwise specified.
+ flags |= Context.RECEIVER_EXPORTED;
+ }
+ } else if ((flags & Context.RECEIVER_NOT_EXPORTED) == 0) {
+ flags |= Context.RECEIVER_EXPORTED;
+ }
+
+ // Dynamic receivers are exported by default for versions prior to T
+ final boolean exported = (flags & Context.RECEIVER_EXPORTED) != 0;
+
+ ArrayList<StickyBroadcast> allSticky = null;
+ if (stickyBroadcasts != null) {
+ final ContentResolver resolver = mContext.getContentResolver();
+ // Look for any matching sticky broadcasts...
+ for (int i = 0, N = stickyBroadcasts.size(); i < N; i++) {
+ final StickyBroadcast broadcast = stickyBroadcasts.get(i);
+ Intent intent = broadcast.intent;
+ // Don't provided intents that aren't available to instant apps.
+ if (instantApp && (intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS)
+ == 0) {
+ continue;
+ }
+ // If intent has scheme "content", it will need to access
+ // provider that needs to lock mProviderMap in ActivityThread
+ // and also it may need to wait application response, so we
+ // cannot lock ActivityManagerService here.
+ final int match;
+ if (Flags.avoidResolvingType()) {
+ match = filter.match(intent.getAction(), broadcast.resolvedDataType,
+ intent.getScheme(), intent.getData(), intent.getCategories(),
+ TAG, false /* supportsWildcards */, null /* ignoreActions */,
+ intent.getExtras());
+ } else {
+ match = filter.match(resolver, intent, true, TAG);
+ }
+ if (match >= 0) {
+ if (allSticky == null) {
+ allSticky = new ArrayList<>();
+ }
+ allSticky.add(broadcast);
+ }
+ }
+ }
+
+ // The first sticky in the list is returned directly back to the client.
+ Intent sticky = allSticky != null ? allSticky.get(0).intent : null;
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);
+ if (receiver == null) {
+ return sticky;
+ }
+
+ // SafetyNet logging for b/177931370. If any process other than system_server tries to
+ // listen to this broadcast action, then log it.
+ if (callingPid != Process.myPid()) {
+ if (filter.hasAction("com.android.server.net.action.SNOOZE_WARNING")
+ || filter.hasAction("com.android.server.net.action.SNOOZE_RAPID")) {
+ EventLog.writeEvent(0x534e4554, "177931370", callingUid, "");
+ }
+ }
+
+ synchronized (mService) {
+ IApplicationThread thread;
+ if (callerApp != null && ((thread = callerApp.getThread()) == null
+ || thread.asBinder() != caller.asBinder())) {
+ // Original caller already died
+ return null;
+ }
+ ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
+ if (rl == null) {
+ rl = new ReceiverList(mService, callerApp, callingPid, callingUid,
+ userId, receiver);
+ if (rl.app != null) {
+ final int totalReceiversForApp = rl.app.mReceivers.numberOfReceivers();
+ if (totalReceiversForApp >= MAX_RECEIVERS_ALLOWED_PER_APP) {
+ throw new IllegalStateException("Too many receivers, total of "
+ + totalReceiversForApp + ", registered for pid: "
+ + rl.pid + ", callerPackage: " + callerPackage);
+ }
+ rl.app.mReceivers.addReceiver(rl);
+ } else {
+ try {
+ receiver.asBinder().linkToDeath(rl, 0);
+ } catch (RemoteException e) {
+ return sticky;
+ }
+ rl.linkedToDeath = true;
+ }
+ mRegisteredReceivers.put(receiver.asBinder(), rl);
+ } else if (rl.uid != callingUid) {
+ throw new IllegalArgumentException(
+ "Receiver requested to register for uid " + callingUid
+ + " was previously registered for uid " + rl.uid
+ + " callerPackage is " + callerPackage);
+ } else if (rl.pid != callingPid) {
+ throw new IllegalArgumentException(
+ "Receiver requested to register for pid " + callingPid
+ + " was previously registered for pid " + rl.pid
+ + " callerPackage is " + callerPackage);
+ } else if (rl.userId != userId) {
+ throw new IllegalArgumentException(
+ "Receiver requested to register for user " + userId
+ + " was previously registered for user " + rl.userId
+ + " callerPackage is " + callerPackage);
+ }
+ BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
+ receiverId, permission, callingUid, userId, instantApp, visibleToInstantApps,
+ exported);
+ if (rl.containsFilter(filter)) {
+ Slog.w(TAG, "Receiver with filter " + filter
+ + " already registered for pid " + rl.pid
+ + ", callerPackage is " + callerPackage);
+ } else {
+ rl.add(bf);
+ if (!bf.debugCheck()) {
+ Slog.w(TAG, "==> For Dynamic broadcast");
+ }
+ mReceiverResolver.addFilter(mService.getPackageManagerInternal().snapshot(), bf);
+ }
+
+ // Enqueue broadcasts for all existing stickies that match
+ // this filter.
+ if (allSticky != null) {
+ ArrayList receivers = new ArrayList();
+ receivers.add(bf);
+ sticky = null;
+
+ final int stickyCount = allSticky.size();
+ for (int i = 0; i < stickyCount; i++) {
+ final StickyBroadcast broadcast = allSticky.get(i);
+ final int originalStickyCallingUid = allSticky.get(i).originalCallingUid;
+ // TODO(b/281889567): consider using checkComponentPermission instead of
+ // canAccessUnexportedComponents
+ if (sticky == null && (exported || originalStickyCallingUid == callingUid
+ || ActivityManager.canAccessUnexportedComponents(
+ originalStickyCallingUid))) {
+ sticky = broadcast.intent;
+ }
+ BroadcastQueue queue = mBroadcastQueue;
+ BroadcastRecord r = new BroadcastRecord(queue, broadcast.intent, null,
+ null, null, -1, -1, false, null, null, null, null, OP_NONE,
+ BroadcastOptions.makeWithDeferUntilActive(broadcast.deferUntilActive),
+ receivers, null, null, 0, null, null, false, true, true, -1,
+ originalStickyCallingUid, BackgroundStartPrivileges.NONE,
+ false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
+ null /* filterExtrasForReceiver */,
+ broadcast.originalCallingAppProcessState);
+ queue.enqueueBroadcastLocked(r);
+ }
+ }
+
+ return sticky;
+ }
+ }
+
+ void unregisterReceiver(IIntentReceiver receiver) {
+ traceUnregistrationBegin(receiver);
+ try {
+ unregisterReceiverTraced(receiver);
+ } finally {
+ traceUnregistrationEnd();
+ }
+ }
+
+ private static void traceUnregistrationBegin(IIntentReceiver receiver) {
+ if (!Flags.traceReceiverRegistration()) {
+ return;
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ TextUtils.formatSimple("unregisterReceiver: %d/%s", Binder.getCallingUid(),
+ receiver == null ? "null" : receiver.asBinder()));
+ }
+ }
+
+ private static void traceUnregistrationEnd() {
+ if (!Flags.traceReceiverRegistration()) {
+ return;
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ }
+
+ private void unregisterReceiverTraced(IIntentReceiver receiver) {
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Unregister receiver: " + receiver);
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ boolean doTrim = false;
+ synchronized (mService) {
+ ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
+ if (rl != null) {
+ final BroadcastRecord r = rl.curBroadcast;
+ if (r != null) {
+ final boolean doNext = r.queue.finishReceiverLocked(
+ rl.app, r.resultCode, r.resultData, r.resultExtras,
+ r.resultAbort, false);
+ if (doNext) {
+ doTrim = true;
+ }
+ }
+ if (rl.app != null) {
+ rl.app.mReceivers.removeReceiver(rl);
+ }
+ removeReceiverLocked(rl);
+ if (rl.linkedToDeath) {
+ rl.linkedToDeath = false;
+ rl.receiver.asBinder().unlinkToDeath(rl, 0);
+ }
+ }
+
+ // If we actually concluded any broadcasts, we might now be able
+ // to trim the recipients' apps from our working set
+ if (doTrim) {
+ mService.trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
+ return;
+ }
+ }
+
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ void removeReceiverLocked(ReceiverList rl) {
+ mRegisteredReceivers.remove(rl.receiver.asBinder());
+ for (int i = rl.size() - 1; i >= 0; i--) {
+ mReceiverResolver.removeFilter(rl.get(i));
+ }
+ }
+
+ int broadcastIntentWithFeature(IApplicationThread caller, String callingFeatureId,
+ Intent intent, String resolvedType, IIntentReceiver resultTo,
+ int resultCode, String resultData, Bundle resultExtras,
+ String[] requiredPermissions, String[] excludedPermissions,
+ String[] excludedPackages, int appOp, Bundle bOptions,
+ boolean serialized, boolean sticky, int userId) {
+ mService.enforceNotIsolatedCaller("broadcastIntent");
+
+ synchronized (mService) {
+ intent = verifyBroadcastLocked(intent);
+
+ final ProcessRecord callerApp = mService.getRecordForAppLOSP(caller);
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+
+ // We're delivering the result to the caller
+ final ProcessRecord resultToApp = callerApp;
+
+ // Permission regimes around sender-supplied broadcast options.
+ enforceBroadcastOptionPermissionsInternal(bOptions, callingUid);
+
+ final ComponentName cn = intent.getComponent();
+
+ Trace.traceBegin(
+ Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "broadcastIntent:" + (cn != null ? cn.toString() : intent.getAction()));
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ return broadcastIntentLocked(callerApp,
+ callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
+ intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
+ resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
+ appOp, bOptions, serialized, sticky, callingPid, callingUid, callingUid,
+ callingPid, userId, BackgroundStartPrivileges.NONE, null, null);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ }
+ }
+
+ // Not the binder call surface
+ int broadcastIntentInPackage(String packageName, @Nullable String featureId, int uid,
+ int realCallingUid, int realCallingPid, Intent intent, String resolvedType,
+ ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode,
+ String resultData, Bundle resultExtras, String requiredPermission, Bundle bOptions,
+ boolean serialized, boolean sticky, int userId,
+ BackgroundStartPrivileges backgroundStartPrivileges,
+ @Nullable int[] broadcastAllowList) {
+ synchronized (mService) {
+ intent = verifyBroadcastLocked(intent);
+
+ final long origId = Binder.clearCallingIdentity();
+ String[] requiredPermissions = requiredPermission == null ? null
+ : new String[] {requiredPermission};
+ try {
+ return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
+ resultToApp, resultTo, resultCode, resultData, resultExtras,
+ requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
+ uid, realCallingUid, realCallingPid, userId,
+ backgroundStartPrivileges, broadcastAllowList,
+ null /* filterExtrasForReceiver */);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+
+ @GuardedBy("mService")
+ final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage,
+ @Nullable String callerFeatureId, Intent intent, String resolvedType,
+ ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
+ Bundle resultExtras, String[] requiredPermissions,
+ String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
+ boolean ordered, boolean sticky, int callingPid, int callingUid,
+ int realCallingUid, int realCallingPid, int userId,
+ BackgroundStartPrivileges backgroundStartPrivileges,
+ @Nullable int[] broadcastAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
+ final int cookie = traceBroadcastIntentBegin(intent, resultTo, ordered, sticky,
+ callingUid, realCallingUid, userId);
+ try {
+ final BroadcastSentEventRecord broadcastSentEventRecord =
+ new BroadcastSentEventRecord();
+ final int res = broadcastIntentLockedTraced(callerApp, callerPackage, callerFeatureId,
+ intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
+ resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
+ appOp, BroadcastOptions.fromBundleNullable(bOptions), ordered, sticky,
+ callingPid, callingUid, realCallingUid, realCallingPid, userId,
+ backgroundStartPrivileges, broadcastAllowList, filterExtrasForReceiver,
+ broadcastSentEventRecord);
+ broadcastSentEventRecord.setResult(res);
+ broadcastSentEventRecord.logToStatsd();
+ return res;
+ } finally {
+ traceBroadcastIntentEnd(cookie);
+ }
+ }
+
+ private static int traceBroadcastIntentBegin(Intent intent, IIntentReceiver resultTo,
+ boolean ordered, boolean sticky, int callingUid, int realCallingUid, int userId) {
+ if (!Flags.traceReceiverRegistration()) {
+ return BroadcastQueue.traceBegin("broadcastIntentLockedTraced");
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ final StringBuilder sb = new StringBuilder("broadcastIntent: ");
+ sb.append(callingUid); sb.append('/');
+ final String action = intent.getAction();
+ sb.append(action == null ? null : action); sb.append('/');
+ sb.append("0x"); sb.append(Integer.toHexString(intent.getFlags())); sb.append('/');
+ sb.append(ordered ? "O" : "_");
+ sb.append(sticky ? "S" : "_");
+ sb.append(resultTo != null ? "C" : "_");
+ sb.append('/');
+ sb.append('u'); sb.append(userId);
+ if (callingUid != realCallingUid) {
+ sb.append('/');
+ sb.append("sender="); sb.append(realCallingUid);
+ }
+ return BroadcastQueue.traceBegin(sb.toString());
+ }
+ return 0;
+ }
+
+ private static void traceBroadcastIntentEnd(int cookie) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ BroadcastQueue.traceEnd(cookie);
+ }
+ }
+
+ @GuardedBy("mService")
+ final int broadcastIntentLockedTraced(ProcessRecord callerApp, String callerPackage,
+ @Nullable String callerFeatureId, Intent intent, String resolvedType,
+ ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
+ Bundle resultExtras, String[] requiredPermissions,
+ String[] excludedPermissions, String[] excludedPackages, int appOp,
+ BroadcastOptions brOptions, boolean ordered, boolean sticky, int callingPid,
+ int callingUid, int realCallingUid, int realCallingPid, int userId,
+ BackgroundStartPrivileges backgroundStartPrivileges,
+ @Nullable int[] broadcastAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+ @NonNull BroadcastSentEventRecord broadcastSentEventRecord) {
+ // Ensure all internal loopers are registered for idle checks
+ BroadcastLoopers.addMyLooper();
+
+ if (Process.isSdkSandboxUid(realCallingUid)) {
+ final SdkSandboxManagerLocal sdkSandboxManagerLocal = LocalManagerRegistry.getManager(
+ SdkSandboxManagerLocal.class);
+ if (sdkSandboxManagerLocal == null) {
+ throw new IllegalStateException("SdkSandboxManagerLocal not found when sending"
+ + " a broadcast from an SDK sandbox uid.");
+ }
+ if (!sdkSandboxManagerLocal.canSendBroadcast(intent)) {
+ throw new SecurityException(
+ "Intent " + intent.getAction() + " may not be broadcast from an SDK sandbox"
+ + " uid. Given caller package " + callerPackage
+ + " (pid=" + callingPid + ", realCallingUid=" + realCallingUid
+ + ", callingUid= " + callingUid + ")");
+ }
+ }
+
+ if ((resultTo != null) && (resultToApp == null)) {
+ if (resultTo.asBinder() instanceof BinderProxy) {
+ // Warn when requesting results without a way to deliver them
+ Slog.wtf(TAG, "Sending broadcast " + intent.getAction()
+ + " with resultTo requires resultToApp", new Throwable());
+ } else {
+ // If not a BinderProxy above, then resultTo is an in-process
+ // receiver, so splice in system_server process
+ resultToApp = mService.getProcessRecordLocked("system", SYSTEM_UID);
+ }
+ }
+
+ intent = new Intent(intent);
+ broadcastSentEventRecord.setIntent(intent);
+ broadcastSentEventRecord.setOriginalIntentFlags(intent.getFlags());
+ broadcastSentEventRecord.setSenderUid(callingUid);
+ broadcastSentEventRecord.setRealSenderUid(realCallingUid);
+ broadcastSentEventRecord.setSticky(sticky);
+ broadcastSentEventRecord.setOrdered(ordered);
+ broadcastSentEventRecord.setResultRequested(resultTo != null);
+ final int callerAppProcessState = getRealProcessStateLocked(callerApp, realCallingPid);
+ broadcastSentEventRecord.setSenderProcState(callerAppProcessState);
+ broadcastSentEventRecord.setSenderUidState(getRealUidStateLocked(callerApp,
+ realCallingPid));
+
+ final boolean callerInstantApp = isInstantApp(callerApp, callerPackage, callingUid);
+ // Instant Apps cannot use FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS
+ if (callerInstantApp) {
+ intent.setFlags(intent.getFlags() & ~Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
+ }
+
+ if (userId == UserHandle.USER_ALL && broadcastAllowList != null) {
+ Slog.e(TAG, "broadcastAllowList only applies when sending to individual users. "
+ + "Assuming restrictive whitelist.");
+ broadcastAllowList = new int[]{};
+ }
+
+ // By default broadcasts do not go to stopped apps.
+ intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
+
+ // If we have not finished booting, don't allow this to launch new processes.
+ if (!mService.mProcessesReady
+ && (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ }
+
+ if (DEBUG_BROADCAST_LIGHT) {
+ Slog.v(TAG_BROADCAST,
+ (sticky ? "Broadcast sticky: " : "Broadcast: ") + intent
+ + " ordered=" + ordered + " userid=" + userId
+ + " options=" + (brOptions == null ? "null" : brOptions.toBundle()));
+ }
+ if ((resultTo != null) && !ordered) {
+ if (!UserHandle.isCore(callingUid)) {
+ String msg = "Unauthorized unordered resultTo broadcast "
+ + intent + " sent from uid " + callingUid;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ }
+
+ userId = mService.mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
+ ALLOW_NON_FULL, "broadcast", callerPackage);
+
+ // Make sure that the user who is receiving this broadcast or its parent is running.
+ // If not, we will just skip it. Make an exception for shutdown broadcasts, upgrade steps.
+ if (userId != UserHandle.USER_ALL && !mService.mUserController.isUserOrItsParentRunning(
+ userId)) {
+ if ((callingUid != SYSTEM_UID
+ || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
+ && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
+ Slog.w(TAG, "Skipping broadcast of " + intent
+ + ": user " + userId + " and its parent (if any) are stopped");
+ scheduleCanceledResultTo(resultToApp, resultTo, intent, userId,
+ brOptions, callingUid, callerPackage);
+ return ActivityManager.BROADCAST_FAILED_USER_STOPPED;
+ }
+ }
+
+ final String action = intent.getAction();
+ if (brOptions != null) {
+ if (brOptions.getTemporaryAppAllowlistDuration() > 0) {
+ // See if the caller is allowed to do this. Note we are checking against
+ // the actual real caller (not whoever provided the operation as say a
+ // PendingIntent), because that who is actually supplied the arguments.
+ if (checkComponentPermission(CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
+ realCallingPid, realCallingUid, -1, true)
+ != PackageManager.PERMISSION_GRANTED
+ && checkComponentPermission(START_ACTIVITIES_FROM_BACKGROUND,
+ realCallingPid, realCallingUid, -1, true)
+ != PackageManager.PERMISSION_GRANTED
+ && checkComponentPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND,
+ realCallingPid, realCallingUid, -1, true)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: " + intent.getAction()
+ + " broadcast from " + callerPackage + " (pid=" + callingPid
+ + ", uid=" + callingUid + ")"
+ + " requires "
+ + CHANGE_DEVICE_IDLE_TEMP_WHITELIST + " or "
+ + START_ACTIVITIES_FROM_BACKGROUND + " or "
+ + START_FOREGROUND_SERVICES_FROM_BACKGROUND;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ }
+ if (brOptions.isDontSendToRestrictedApps()
+ && !mService.isUidActiveLOSP(callingUid)
+ && mService.isBackgroundRestrictedNoCheck(callingUid, callerPackage)) {
+ Slog.i(TAG, "Not sending broadcast " + action + " - app " + callerPackage
+ + " has background restrictions");
+ return ActivityManager.START_CANCELED;
+ }
+ if (brOptions.allowsBackgroundActivityStarts()) {
+ // See if the caller is allowed to do this. Note we are checking against
+ // the actual real caller (not whoever provided the operation as say a
+ // PendingIntent), because that who is actually supplied the arguments.
+ if (checkComponentPermission(
+ android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
+ realCallingPid, realCallingUid, -1, true)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: " + intent.getAction()
+ + " broadcast from " + callerPackage + " (pid=" + callingPid
+ + ", uid=" + callingUid + ")"
+ + " requires "
+ + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ } else {
+ // We set the token to null since if it wasn't for it we'd allow anyway here
+ backgroundStartPrivileges = BackgroundStartPrivileges.ALLOW_BAL;
+ }
+ }
+
+ if (brOptions.getIdForResponseEvent() > 0) {
+ mService.enforcePermission(
+ android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS,
+ callingPid, callingUid, "recordResponseEventWhileInBackground");
+ }
+ }
+
+ // Verify that protected broadcasts are only being sent by system code,
+ // and that system code is only sending protected broadcasts.
+ final boolean isProtectedBroadcast;
+ try {
+ isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Remote exception", e);
+ scheduleCanceledResultTo(resultToApp, resultTo, intent,
+ userId, brOptions, callingUid, callerPackage);
+ return ActivityManager.BROADCAST_SUCCESS;
+ }
+
+ final boolean isCallerSystem;
+ switch (UserHandle.getAppId(callingUid)) {
+ case ROOT_UID:
+ case SYSTEM_UID:
+ case PHONE_UID:
+ case BLUETOOTH_UID:
+ case NFC_UID:
+ case SE_UID:
+ case NETWORK_STACK_UID:
+ isCallerSystem = true;
+ break;
+ default:
+ isCallerSystem = (callerApp != null) && callerApp.isPersistent();
+ break;
+ }
+
+ // First line security check before anything else: stop non-system apps from
+ // sending protected broadcasts.
+ if (!isCallerSystem) {
+ if (isProtectedBroadcast) {
+ String msg = "Permission Denial: not allowed to send broadcast "
+ + action + " from pid="
+ + callingPid + ", uid=" + callingUid;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+
+ } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
+ || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
+ // Special case for compatibility: we don't want apps to send this,
+ // but historically it has not been protected and apps may be using it
+ // to poke their own app widget. So, instead of making it protected,
+ // just limit it to the caller.
+ if (callerPackage == null) {
+ String msg = "Permission Denial: not allowed to send broadcast "
+ + action + " from unknown caller.";
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ } else if (intent.getComponent() != null) {
+ // They are good enough to send to an explicit component... verify
+ // it is being sent to the calling app.
+ if (!intent.getComponent().getPackageName().equals(
+ callerPackage)) {
+ String msg = "Permission Denial: not allowed to send broadcast "
+ + action + " to "
+ + intent.getComponent().getPackageName() + " from "
+ + callerPackage;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ } else {
+ // Limit broadcast to their own package.
+ intent.setPackage(callerPackage);
+ }
+ }
+ }
+
+ boolean timeoutExempt = false;
+
+ if (action != null) {
+ if (getBackgroundLaunchBroadcasts().contains(action)) {
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "Broadcast action " + action + " forcing include-background");
+ }
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ }
+
+ // TODO: b/329211459 - Remove this after background remote intent is fixed.
+ if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+ && getWearRemoteIntentAction().equals(action)) {
+ final int callerProcState = callerApp != null
+ ? callerApp.getCurProcState()
+ : ActivityManager.PROCESS_STATE_NONEXISTENT;
+ if (ActivityManager.RunningAppProcessInfo.procStateToImportance(callerProcState)
+ > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+ return ActivityManager.START_CANCELED;
+ }
+ }
+
+ switch (action) {
+ case Intent.ACTION_MEDIA_SCANNER_SCAN_FILE:
+ UserManagerInternal umInternal = LocalServices.getService(
+ UserManagerInternal.class);
+ UserInfo userInfo = umInternal.getUserInfo(userId);
+ if (userInfo != null && userInfo.isCloneProfile()) {
+ userId = umInternal.getProfileParentId(userId);
+ }
+ break;
+ case Intent.ACTION_UID_REMOVED:
+ case Intent.ACTION_PACKAGE_REMOVED:
+ case Intent.ACTION_PACKAGE_CHANGED:
+ case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+ case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
+ case Intent.ACTION_PACKAGES_SUSPENDED:
+ case Intent.ACTION_PACKAGES_UNSUSPENDED:
+ // Handle special intents: if this broadcast is from the package
+ // manager about a package being removed, we need to remove all of
+ // its activities from the history stack.
+ if (checkComponentPermission(
+ android.Manifest.permission.BROADCAST_PACKAGE_REMOVED,
+ callingPid, callingUid, -1, true)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: " + intent.getAction()
+ + " broadcast from " + callerPackage + " (pid=" + callingPid
+ + ", uid=" + callingUid + ")"
+ + " requires "
+ + android.Manifest.permission.BROADCAST_PACKAGE_REMOVED;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ switch (action) {
+ case Intent.ACTION_UID_REMOVED:
+ final int uid = getUidFromIntent(intent);
+ if (uid >= 0) {
+ mService.mBatteryStatsService.removeUid(uid);
+ if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ mService.mAppOpsService.resetAllModes(UserHandle.getUserId(uid),
+ intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
+ } else {
+ mService.mAppOpsService.uidRemoved(uid);
+ mService.mServices.onUidRemovedLocked(uid);
+ }
+ }
+ break;
+ case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+ // If resources are unavailable just force stop all those packages
+ // and flush the attribute cache as well.
+ String[] list = intent.getStringArrayExtra(
+ Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ if (list != null && list.length > 0) {
+ for (int i = 0; i < list.length; i++) {
+ mService.forceStopPackageLocked(list[i], -1, false, true, true,
+ false, false, false, userId, "storage unmount");
+ }
+ mService.mAtmInternal.cleanupRecentTasksForUser(
+ UserHandle.USER_ALL);
+ sendPackageBroadcastLocked(
+ ApplicationThreadConstants.EXTERNAL_STORAGE_UNAVAILABLE,
+ list, userId);
+ }
+ break;
+ case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
+ mService.mAtmInternal.cleanupRecentTasksForUser(UserHandle.USER_ALL);
+ break;
+ case Intent.ACTION_PACKAGE_REMOVED:
+ case Intent.ACTION_PACKAGE_CHANGED:
+ Uri data = intent.getData();
+ String ssp;
+ if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+ boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals(action);
+ final boolean replacing =
+ intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+ final boolean killProcess =
+ !intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false);
+ final boolean fullUninstall = removed && !replacing;
+
+ if (removed) {
+ if (killProcess) {
+ mService.forceStopPackageLocked(ssp, UserHandle.getAppId(
+ intent.getIntExtra(Intent.EXTRA_UID, -1)),
+ false, true, true, false, fullUninstall, false,
+ userId, "pkg removed");
+ mService.getPackageManagerInternal()
+ .onPackageProcessKilledForUninstall(ssp);
+ } else {
+ // Kill any app zygotes always, since they can't fork new
+ // processes with references to the old code
+ mService.forceStopAppZygoteLocked(ssp, UserHandle.getAppId(
+ intent.getIntExtra(Intent.EXTRA_UID, -1)),
+ userId);
+ }
+ final int cmd = killProcess
+ ? ApplicationThreadConstants.PACKAGE_REMOVED
+ : ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL;
+ sendPackageBroadcastLocked(cmd,
+ new String[] {ssp}, userId);
+ if (fullUninstall) {
+ // Remove all permissions granted from/to this package
+ mService.mUgmInternal.removeUriPermissionsForPackage(ssp,
+ userId, true, false);
+
+ mService.mAtmInternal.removeRecentTasksByPackageName(ssp,
+ userId);
+
+ mService.mServices.forceStopPackageLocked(ssp, userId);
+ mService.mAtmInternal.onPackageUninstalled(ssp, userId);
+ mService.mBatteryStatsService.notePackageUninstalled(ssp);
+ }
+ } else {
+ if (killProcess) {
+ int reason;
+ int subReason;
+ if (replacing) {
+ reason = ApplicationExitInfo.REASON_PACKAGE_UPDATED;
+ subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
+ } else {
+ reason =
+ ApplicationExitInfo.REASON_PACKAGE_STATE_CHANGE;
+ subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
+ }
+
+ final int extraUid = intent.getIntExtra(Intent.EXTRA_UID,
+ -1);
+ synchronized (mService.mProcLock) {
+ mService.mProcessList.killPackageProcessesLSP(ssp,
+ UserHandle.getAppId(extraUid),
+ userId, ProcessList.INVALID_ADJ,
+ reason,
+ subReason,
+ "change " + ssp);
+ }
+ }
+ mService.cleanupDisabledPackageComponentsLocked(ssp, userId,
+ intent.getStringArrayExtra(
+ Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST));
+ mService.mServices.schedulePendingServiceStartLocked(
+ ssp, userId);
+ }
+ }
+ break;
+ case Intent.ACTION_PACKAGES_SUSPENDED:
+ case Intent.ACTION_PACKAGES_UNSUSPENDED:
+ final boolean suspended = Intent.ACTION_PACKAGES_SUSPENDED.equals(
+ intent.getAction());
+ final String[] packageNames = intent.getStringArrayExtra(
+ Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ final int userIdExtra = intent.getIntExtra(
+ Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+
+ mService.mAtmInternal.onPackagesSuspendedChanged(packageNames,
+ suspended, userIdExtra);
+
+ final boolean quarantined = intent.getBooleanExtra(
+ Intent.EXTRA_QUARANTINED, false);
+ if (suspended && quarantined && packageNames != null) {
+ for (int i = 0; i < packageNames.length; i++) {
+ mService.forceStopPackage(packageNames[i], userId,
+ ActivityManager.FLAG_OR_STOPPED, "quarantined");
+ }
+ }
+
+ break;
+ }
+ break;
+ case Intent.ACTION_PACKAGE_REPLACED: {
+ final Uri data = intent.getData();
+ final String ssp;
+ if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+ ApplicationInfo aInfo = null;
+ try {
+ aInfo = AppGlobals.getPackageManager()
+ .getApplicationInfo(ssp, STOCK_PM_FLAGS, userId);
+ } catch (RemoteException ignore) {
+ }
+ if (aInfo == null) {
+ Slog.w(TAG, "Dropping ACTION_PACKAGE_REPLACED for non-existent pkg:"
+ + " ssp=" + ssp + " data=" + data);
+ scheduleCanceledResultTo(resultToApp, resultTo, intent,
+ userId, brOptions, callingUid, callerPackage);
+ return ActivityManager.BROADCAST_SUCCESS;
+ }
+ mService.updateAssociationForApp(aInfo);
+ mService.mAtmInternal.onPackageReplaced(aInfo);
+ mService.mServices.updateServiceApplicationInfoLocked(aInfo);
+ sendPackageBroadcastLocked(ApplicationThreadConstants.PACKAGE_REPLACED,
+ new String[] {ssp}, userId);
+ }
+ break;
+ }
+ case Intent.ACTION_PACKAGE_ADDED: {
+ // Special case for adding a package: by default turn on compatibility mode.
+ Uri data = intent.getData();
+ String ssp;
+ if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+ final boolean replacing =
+ intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+ mService.mAtmInternal.onPackageAdded(ssp, replacing);
+
+ try {
+ ApplicationInfo ai = AppGlobals.getPackageManager()
+ .getApplicationInfo(ssp, STOCK_PM_FLAGS, 0);
+ mService.mBatteryStatsService.notePackageInstalled(ssp,
+ ai != null ? ai.longVersionCode : 0);
+ } catch (RemoteException e) {
+ }
+ }
+ break;
+ }
+ case Intent.ACTION_PACKAGE_DATA_CLEARED: {
+ Uri data = intent.getData();
+ String ssp;
+ if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+ mService.mAtmInternal.onPackageDataCleared(ssp, userId);
+ }
+ break;
+ }
+ case Intent.ACTION_TIMEZONE_CHANGED:
+ // If this is the time zone changed action, queue up a message that will reset
+ // the timezone of all currently running processes. This message will get
+ // queued up before the broadcast happens.
+ mService.mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
+ break;
+ case Intent.ACTION_TIME_CHANGED:
+ // EXTRA_TIME_PREF_24_HOUR_FORMAT is optional so we must distinguish between
+ // the tri-state value it may contain and "unknown".
+ // For convenience we re-use the Intent extra values.
+ final int NO_EXTRA_VALUE_FOUND = -1;
+ final int timeFormatPreferenceMsgValue = intent.getIntExtra(
+ Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT,
+ NO_EXTRA_VALUE_FOUND /* defaultValue */);
+ // Only send a message if the time preference is available.
+ if (timeFormatPreferenceMsgValue != NO_EXTRA_VALUE_FOUND) {
+ Message updateTimePreferenceMsg =
+ mService.mHandler.obtainMessage(UPDATE_TIME_PREFERENCE_MSG,
+ timeFormatPreferenceMsgValue, 0);
+ mService.mHandler.sendMessage(updateTimePreferenceMsg);
+ }
+ mService.mBatteryStatsService.noteCurrentTimeChanged();
+ break;
+ case ConnectivityManager.ACTION_CLEAR_DNS_CACHE:
+ mService.mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);
+ break;
+ case Proxy.PROXY_CHANGE_ACTION:
+ mService.mHandler.sendMessage(mService.mHandler.obtainMessage(
+ UPDATE_HTTP_PROXY_MSG));
+ break;
+ case android.hardware.Camera.ACTION_NEW_PICTURE:
+ case android.hardware.Camera.ACTION_NEW_VIDEO:
+ // In N we just turned these off; in O we are turing them back on partly,
+ // only for registered receivers. This will still address the main problem
+ // (a spam of apps waking up when a picture is taken putting significant
+ // memory pressure on the system at a bad point), while still allowing apps
+ // that are already actively running to know about this happening.
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ break;
+ case android.security.KeyChain.ACTION_TRUST_STORE_CHANGED:
+ mService.mHandler.sendEmptyMessage(HANDLE_TRUST_STORAGE_UPDATE_MSG);
+ break;
+ case "com.android.launcher.action.INSTALL_SHORTCUT":
+ // As of O, we no longer support this broadcasts, even for pre-O apps.
+ // Apps should now be using ShortcutManager.pinRequestShortcut().
+ Log.w(TAG, "Broadcast " + action
+ + " no longer supported. It will not be delivered.");
+ scheduleCanceledResultTo(resultToApp, resultTo, intent,
+ userId, brOptions, callingUid, callerPackage);
+ return ActivityManager.BROADCAST_SUCCESS;
+ case Intent.ACTION_PRE_BOOT_COMPLETED:
+ timeoutExempt = true;
+ break;
+ case Intent.ACTION_CLOSE_SYSTEM_DIALOGS:
+ if (!mService.mAtmInternal.checkCanCloseSystemDialogs(callingPid, callingUid,
+ callerPackage)) {
+ scheduleCanceledResultTo(resultToApp, resultTo, intent,
+ userId, brOptions, callingUid, callerPackage);
+ // Returning success seems to be the pattern here
+ return ActivityManager.BROADCAST_SUCCESS;
+ }
+ break;
+ }
+
+ if (Intent.ACTION_PACKAGE_ADDED.equals(action)
+ || Intent.ACTION_PACKAGE_REMOVED.equals(action)
+ || Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
+ final int uid = getUidFromIntent(intent);
+ if (uid != -1) {
+ final UidRecord uidRec = mService.mProcessList.getUidRecordLOSP(uid);
+ if (uidRec != null) {
+ uidRec.updateHasInternetPermission();
+ }
+ }
+ }
+ }
+
+ // Add to the sticky list if requested.
+ if (sticky) {
+ if (mService.checkPermission(android.Manifest.permission.BROADCAST_STICKY,
+ callingPid, callingUid)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg =
+ "Permission Denial: broadcastIntent() requesting a sticky broadcast from"
+ + " pid="
+ + callingPid
+ + ", uid="
+ + callingUid
+ + " requires "
+ + android.Manifest.permission.BROADCAST_STICKY;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ if (requiredPermissions != null && requiredPermissions.length > 0) {
+ Slog.w(TAG, "Can't broadcast sticky intent " + intent
+ + " and enforce permissions " + Arrays.toString(requiredPermissions));
+ scheduleCanceledResultTo(resultToApp, resultTo, intent,
+ userId, brOptions, callingUid, callerPackage);
+ return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;
+ }
+ if (intent.getComponent() != null) {
+ throw new SecurityException(
+ "Sticky broadcasts can't target a specific component");
+ }
+ synchronized (mStickyBroadcasts) {
+ // We use userId directly here, since the "all" target is maintained
+ // as a separate set of sticky broadcasts.
+ if (userId != UserHandle.USER_ALL) {
+ // But first, if this is not a broadcast to all users, then
+ // make sure it doesn't conflict with an existing broadcast to
+ // all users.
+ ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(
+ UserHandle.USER_ALL);
+ if (stickies != null) {
+ ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
+ if (list != null) {
+ int N = list.size();
+ int i;
+ for (i = 0; i < N; i++) {
+ if (intent.filterEquals(list.get(i).intent)) {
+ throw new IllegalArgumentException("Sticky broadcast " + intent
+ + " for user " + userId
+ + " conflicts with existing global broadcast");
+ }
+ }
+ }
+ }
+ }
+ ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
+ mStickyBroadcasts.get(userId);
+ if (stickies == null) {
+ stickies = new ArrayMap<>();
+ mStickyBroadcasts.put(userId, stickies);
+ }
+ ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
+ if (list == null) {
+ list = new ArrayList<>();
+ stickies.put(intent.getAction(), list);
+ }
+ final boolean deferUntilActive = BroadcastRecord.calculateDeferUntilActive(
+ callingUid, brOptions, resultTo, ordered,
+ BroadcastRecord.calculateUrgent(intent, brOptions));
+ final int stickiesCount = list.size();
+ int i;
+ for (i = 0; i < stickiesCount; i++) {
+ if (intent.filterEquals(list.get(i).intent)) {
+ // This sticky already exists, replace it.
+ list.set(i, StickyBroadcast.create(new Intent(intent), deferUntilActive,
+ callingUid, callerAppProcessState, resolvedType));
+ break;
+ }
+ }
+ if (i >= stickiesCount) {
+ list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
+ callingUid, callerAppProcessState, resolvedType));
+ }
+ }
+ }
+
+ int[] users;
+ if (userId == UserHandle.USER_ALL) {
+ // Caller wants broadcast to go to all started users.
+ users = mService.mUserController.getStartedUserArray();
+ } else {
+ // Caller wants broadcast to go to one specific user.
+ users = new int[] {userId};
+ }
+
+ var args = new SaferIntentUtils.IntentArgs(intent, resolvedType,
+ true /* isReceiver */, true /* resolveForStart */, callingUid, callingPid);
+ args.platformCompat = mService.mPlatformCompat;
+
+ // Figure out who all will receive this broadcast.
+ final int cookie = BroadcastQueue.traceBegin("queryReceivers");
+ List receivers = null;
+ List<BroadcastFilter> registeredReceivers = null;
+ // Need to resolve the intent to interested receivers...
+ if ((intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+ receivers = collectReceiverComponents(
+ intent, resolvedType, callingUid, callingPid, users, broadcastAllowList);
+ }
+ if (intent.getComponent() == null) {
+ final PackageDataSnapshot snapshot = mService.getPackageManagerInternal().snapshot();
+ if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
+ // Query one target user at a time, excluding shell-restricted users
+ for (int i = 0; i < users.length; i++) {
+ if (mService.mUserController.hasUserRestriction(
+ UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {
+ continue;
+ }
+ List<BroadcastFilter> registeredReceiversForUser =
+ mReceiverResolver.queryIntent(snapshot, intent,
+ resolvedType, false /*defaultOnly*/, users[i]);
+ if (registeredReceivers == null) {
+ registeredReceivers = registeredReceiversForUser;
+ } else if (registeredReceiversForUser != null) {
+ registeredReceivers.addAll(registeredReceiversForUser);
+ }
+ }
+ } else {
+ registeredReceivers = mReceiverResolver.queryIntent(snapshot, intent,
+ resolvedType, false /*defaultOnly*/, userId);
+ }
+ if (registeredReceivers != null) {
+ SaferIntentUtils.blockNullAction(args, registeredReceivers);
+ }
+ }
+ BroadcastQueue.traceEnd(cookie);
+
+ final boolean replacePending =
+ (intent.getFlags() & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
+
+ if (DEBUG_BROADCAST) {
+ Slog.v(TAG_BROADCAST, "Enqueueing broadcast: " + intent.getAction()
+ + " replacePending=" + replacePending);
+ }
+ if (registeredReceivers != null && broadcastAllowList != null) {
+ // if a uid whitelist was provided, remove anything in the application space that wasn't
+ // in it.
+ for (int i = registeredReceivers.size() - 1; i >= 0; i--) {
+ final int owningAppId = UserHandle.getAppId(registeredReceivers.get(i).owningUid);
+ if (owningAppId >= Process.FIRST_APPLICATION_UID
+ && Arrays.binarySearch(broadcastAllowList, owningAppId) < 0) {
+ registeredReceivers.remove(i);
+ }
+ }
+ }
+
+ int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
+
+ // Merge into one list.
+ int ir = 0;
+ if (receivers != null) {
+ // A special case for PACKAGE_ADDED: do not allow the package
+ // being added to see this broadcast. This prevents them from
+ // using this as a back door to get run as soon as they are
+ // installed. Maybe in the future we want to have a special install
+ // broadcast or such for apps, but we'd like to deliberately make
+ // this decision.
+ String[] skipPackages = null;
+ if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())
+ || Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())
+ || Intent.ACTION_PACKAGE_DATA_CLEARED.equals(intent.getAction())) {
+ Uri data = intent.getData();
+ if (data != null) {
+ String pkgName = data.getSchemeSpecificPart();
+ if (pkgName != null) {
+ skipPackages = new String[] { pkgName };
+ }
+ }
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {
+ skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ }
+ if (skipPackages != null && (skipPackages.length > 0)) {
+ for (String skipPackage : skipPackages) {
+ if (skipPackage != null) {
+ int NT = receivers.size();
+ for (int it = 0; it < NT; it++) {
+ ResolveInfo curt = (ResolveInfo) receivers.get(it);
+ if (curt.activityInfo.packageName.equals(skipPackage)) {
+ receivers.remove(it);
+ it--;
+ NT--;
+ }
+ }
+ }
+ }
+ }
+
+ int NT = receivers != null ? receivers.size() : 0;
+ int it = 0;
+ ResolveInfo curt = null;
+ BroadcastFilter curr = null;
+ while (it < NT && ir < NR) {
+ if (curt == null) {
+ curt = (ResolveInfo) receivers.get(it);
+ }
+ if (curr == null) {
+ curr = registeredReceivers.get(ir);
+ }
+ if (curr.getPriority() >= curt.priority) {
+ // Insert this broadcast record into the final list.
+ receivers.add(it, curr);
+ ir++;
+ curr = null;
+ it++;
+ NT++;
+ } else {
+ // Skip to the next ResolveInfo in the final list.
+ it++;
+ curt = null;
+ }
+ }
+ }
+ while (ir < NR) {
+ if (receivers == null) {
+ receivers = new ArrayList();
+ }
+ receivers.add(registeredReceivers.get(ir));
+ ir++;
+ }
+
+ if (isCallerSystem) {
+ checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
+ isProtectedBroadcast, receivers);
+ }
+
+ if ((receivers != null && receivers.size() > 0)
+ || resultTo != null) {
+ BroadcastQueue queue = mBroadcastQueue;
+ SaferIntentUtils.filterNonExportedComponents(args, receivers);
+ BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
+ callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
+ requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
+ receivers, resultToApp, resultTo, resultCode, resultData, resultExtras,
+ ordered, sticky, false, userId,
+ backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
+ callerAppProcessState);
+ broadcastSentEventRecord.setBroadcastRecord(r);
+
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
+ queue.enqueueBroadcastLocked(r);
+ } else {
+ // There was nobody interested in the broadcast, but we still want to record
+ // that it happened.
+ if (intent.getComponent() == null && intent.getPackage() == null
+ && (intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+ // This was an implicit broadcast... let's record it for posterity.
+ addBroadcastStatLocked(intent.getAction(), callerPackage, 0, 0, 0);
+ }
+ }
+
+ return ActivityManager.BROADCAST_SUCCESS;
+ }
+
+ @GuardedBy("mService")
+ private void scheduleCanceledResultTo(ProcessRecord resultToApp, IIntentReceiver resultTo,
+ Intent intent, int userId, BroadcastOptions options, int callingUid,
+ String callingPackage) {
+ if (resultTo == null) {
+ return;
+ }
+ final ProcessRecord app = resultToApp;
+ final IApplicationThread thread = (app != null) ? app.getOnewayThread() : null;
+ if (thread != null) {
+ try {
+ final boolean shareIdentity = (options != null && options.isShareIdentityEnabled());
+ thread.scheduleRegisteredReceiver(
+ resultTo, intent, Activity.RESULT_CANCELED, null, null,
+ false, false, true, userId, app.mState.getReportedProcState(),
+ shareIdentity ? callingUid : Process.INVALID_UID,
+ shareIdentity ? callingPackage : null);
+ } catch (RemoteException e) {
+ final String msg = "Failed to schedule result of " + intent + " via "
+ + app + ": " + e;
+ app.killLocked("Can't schedule resultTo", ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
+ Slog.d(TAG, msg);
+ }
+ }
+ }
+
+ @GuardedBy("mService")
+ private int getRealProcessStateLocked(ProcessRecord app, int pid) {
+ if (app == null) {
+ synchronized (mService.mPidsSelfLocked) {
+ app = mService.mPidsSelfLocked.get(pid);
+ }
+ }
+ if (app != null && app.getThread() != null && !app.isKilled()) {
+ return app.mState.getCurProcState();
+ }
+ return PROCESS_STATE_NONEXISTENT;
+ }
+
+ @GuardedBy("mService")
+ private int getRealUidStateLocked(ProcessRecord app, int pid) {
+ if (app == null) {
+ synchronized (mService.mPidsSelfLocked) {
+ app = mService.mPidsSelfLocked.get(pid);
+ }
+ }
+ if (app != null && app.getThread() != null && !app.isKilled()) {
+ final UidRecord uidRecord = app.getUidRecord();
+ if (uidRecord != null) {
+ return uidRecord.getCurProcState();
+ }
+ }
+ return PROCESS_STATE_NONEXISTENT;
+ }
+
+ @VisibleForTesting
+ ArrayList<StickyBroadcast> getStickyBroadcastsForTest(String action, int userId) {
+ synchronized (mStickyBroadcasts) {
+ final ArrayMap<String, ArrayList<StickyBroadcast>> stickyBroadcasts =
+ mStickyBroadcasts.get(userId);
+ if (stickyBroadcasts == null) {
+ return null;
+ }
+ return stickyBroadcasts.get(action);
+ }
+ }
+
+ void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) {
+ // Refuse possible leaked file descriptors
+ if (intent != null && intent.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Intent");
+ }
+
+ userId = mService.mUserController.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, true, ALLOW_NON_FULL,
+ "removeStickyBroadcast", null);
+
+ if (mService.checkCallingPermission(android.Manifest.permission.BROADCAST_STICKY)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: unbroadcastIntent() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + android.Manifest.permission.BROADCAST_STICKY;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ synchronized (mStickyBroadcasts) {
+ ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
+ if (stickies != null) {
+ ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
+ if (list != null) {
+ int N = list.size();
+ int i;
+ for (i = 0; i < N; i++) {
+ if (intent.filterEquals(list.get(i).intent)) {
+ list.remove(i);
+ break;
+ }
+ }
+ if (list.size() <= 0) {
+ stickies.remove(intent.getAction());
+ }
+ }
+ if (stickies.size() <= 0) {
+ mStickyBroadcasts.remove(userId);
+ }
+ }
+ }
+ }
+
+ void finishReceiver(IBinder caller, int resultCode, String resultData,
+ Bundle resultExtras, boolean resultAbort, int flags) {
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Finish receiver: " + caller);
+
+ // Refuse possible leaked file descriptors
+ if (resultExtras != null && resultExtras.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Bundle");
+ }
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mService) {
+ final ProcessRecord callerApp = mService.getRecordForAppLOSP(caller);
+ if (callerApp == null) {
+ Slog.w(TAG, "finishReceiver: no app for " + caller);
+ return;
+ }
+
+ mBroadcastQueue.finishReceiverLocked(callerApp, resultCode,
+ resultData, resultExtras, resultAbort, true);
+ // updateOomAdjLocked() will be done here
+ mService.trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
+ }
+
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ /**
+ * @return uid from the extra field {@link Intent#EXTRA_UID} if present, Otherwise -1
+ */
+ private int getUidFromIntent(Intent intent) {
+ if (intent == null) {
+ return -1;
+ }
+ final Bundle intentExtras = intent.getExtras();
+ return intent.hasExtra(Intent.EXTRA_UID)
+ ? intentExtras.getInt(Intent.EXTRA_UID) : -1;
+ }
+
+ final void rotateBroadcastStatsIfNeededLocked() {
+ final long now = SystemClock.elapsedRealtime();
+ if (mCurBroadcastStats == null
+ || (mCurBroadcastStats.mStartRealtime + (24 * 60 * 60 * 1000) < now)) {
+ mLastBroadcastStats = mCurBroadcastStats;
+ if (mLastBroadcastStats != null) {
+ mLastBroadcastStats.mEndRealtime = SystemClock.elapsedRealtime();
+ mLastBroadcastStats.mEndUptime = SystemClock.uptimeMillis();
+ }
+ mCurBroadcastStats = new BroadcastStats();
+ }
+ }
+
+ final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
+ int skipCount, long dispatchTime) {
+ rotateBroadcastStatsIfNeededLocked();
+ mCurBroadcastStats.addBroadcast(action, srcPackage, receiveCount, skipCount, dispatchTime);
+ }
+
+ final void addBackgroundCheckViolationLocked(String action, String targetPackage) {
+ rotateBroadcastStatsIfNeededLocked();
+ mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage);
+ }
+
+ final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
+ final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
+ final String callerPackage = info != null ? info.packageName : original.callerPackage;
+ if (callerPackage != null) {
+ mService.mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
+ original.callingUid, 0, callerPackage).sendToTarget();
+ }
+ }
+
+ final Intent verifyBroadcastLocked(Intent intent) {
+ if (intent != null) {
+ intent.prepareToEnterSystemServer();
+ }
+
+ int flags = intent.getFlags();
+
+ if (!mService.mProcessesReady) {
+ // if the caller really truly claims to know what they're doing, go
+ // ahead and allow the broadcast without launching any receivers
+ if ((flags & Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) != 0) {
+ // This will be turned into a FLAG_RECEIVER_REGISTERED_ONLY later on if needed.
+ } else if ((flags & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+ Slog.e(TAG, "Attempt to launch receivers of broadcast intent " + intent
+ + " before boot completion");
+ throw new IllegalStateException("Cannot broadcast before boot completed");
+ }
+ }
+
+ if ((flags & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
+ throw new IllegalArgumentException(
+ "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
+ }
+
+ if ((flags & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
+ switch (Binder.getCallingUid()) {
+ case ROOT_UID:
+ case SHELL_UID:
+ break;
+ default:
+ Slog.w(TAG, "Removing FLAG_RECEIVER_FROM_SHELL because caller is UID "
+ + Binder.getCallingUid());
+ intent.removeFlags(Intent.FLAG_RECEIVER_FROM_SHELL);
+ break;
+ }
+ }
+
+ return intent;
+ }
+
+ private ArraySet<String> getBackgroundLaunchBroadcasts() {
+ if (mBackgroundLaunchBroadcasts == null) {
+ mBackgroundLaunchBroadcasts = SystemConfig.getInstance().getAllowImplicitBroadcasts();
+ }
+ return mBackgroundLaunchBroadcasts;
+ }
+
+ private boolean isInstantApp(ProcessRecord record, @Nullable String callerPackage, int uid) {
+ if (UserHandle.getAppId(uid) < FIRST_APPLICATION_UID) {
+ return false;
+ }
+ // Easy case -- we have the app's ProcessRecord.
+ if (record != null) {
+ return record.info.isInstantApp();
+ }
+ // Otherwise check with PackageManager.
+ IPackageManager pm = AppGlobals.getPackageManager();
+ try {
+ if (callerPackage == null) {
+ final String[] packageNames = pm.getPackagesForUid(uid);
+ if (packageNames == null || packageNames.length == 0) {
+ throw new IllegalArgumentException("Unable to determine caller package name");
+ }
+ // Instant Apps can't use shared uids, so its safe to only check the first package.
+ callerPackage = packageNames[0];
+ }
+ mService.mAppOpsService.checkPackage(uid, callerPackage);
+ return pm.isInstantApp(callerPackage, UserHandle.getUserId(uid));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error looking up if " + callerPackage + " is an instant app.", e);
+ return true;
+ }
+ }
+
+ private String getWearRemoteIntentAction() {
+ return mContext.getResources().getString(
+ com.android.internal.R.string.config_wearRemoteIntentAction);
+ }
+
+ private void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
+ mService.mProcessList.sendPackageBroadcastLocked(cmd, packages, userId);
+ }private List<ResolveInfo> collectReceiverComponents(
+ Intent intent, String resolvedType, int callingUid, int callingPid,
+ int[] users, int[] broadcastAllowList) {
+ // TODO: come back and remove this assumption to triage all broadcasts
+ long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;
+
+ List<ResolveInfo> receivers = null;
+ HashSet<ComponentName> singleUserReceivers = null;
+ boolean scannedFirstReceivers = false;
+ for (int user : users) {
+ // Skip users that have Shell restrictions
+ if (callingUid == SHELL_UID
+ && mService.mUserController.hasUserRestriction(
+ UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
+ continue;
+ }
+ List<ResolveInfo> newReceivers = mService.mPackageManagerInt.queryIntentReceivers(
+ intent, resolvedType, pmFlags, callingUid, callingPid, user, /* forSend */true);
+ if (user != UserHandle.USER_SYSTEM && newReceivers != null) {
+ // If this is not the system user, we need to check for
+ // any receivers that should be filtered out.
+ for (int i = 0; i < newReceivers.size(); i++) {
+ ResolveInfo ri = newReceivers.get(i);
+ if ((ri.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
+ newReceivers.remove(i);
+ i--;
+ }
+ }
+ }
+ // Replace the alias receivers with their targets.
+ if (newReceivers != null) {
+ for (int i = newReceivers.size() - 1; i >= 0; i--) {
+ final ResolveInfo ri = newReceivers.get(i);
+ final ComponentAliasResolver.Resolution<ResolveInfo> resolution =
+ mService.mComponentAliasResolver.resolveReceiver(intent, ri,
+ resolvedType, pmFlags, user, callingUid, callingPid);
+ if (resolution == null) {
+ // It was an alias, but the target was not found.
+ newReceivers.remove(i);
+ continue;
+ }
+ if (resolution.isAlias()) {
+ newReceivers.set(i, resolution.getTarget());
+ }
+ }
+ }
+ if (newReceivers != null && newReceivers.size() == 0) {
+ newReceivers = null;
+ }
+
+ if (receivers == null) {
+ receivers = newReceivers;
+ } else if (newReceivers != null) {
+ // We need to concatenate the additional receivers
+ // found with what we have do far. This would be easy,
+ // but we also need to de-dup any receivers that are
+ // singleUser.
+ if (!scannedFirstReceivers) {
+ // Collect any single user receivers we had already retrieved.
+ scannedFirstReceivers = true;
+ for (int i = 0; i < receivers.size(); i++) {
+ ResolveInfo ri = receivers.get(i);
+ if ((ri.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
+ ComponentName cn = new ComponentName(
+ ri.activityInfo.packageName, ri.activityInfo.name);
+ if (singleUserReceivers == null) {
+ singleUserReceivers = new HashSet<ComponentName>();
+ }
+ singleUserReceivers.add(cn);
+ }
+ }
+ }
+ // Add the new results to the existing results, tracking
+ // and de-dupping single user receivers.
+ for (int i = 0; i < newReceivers.size(); i++) {
+ ResolveInfo ri = newReceivers.get(i);
+ if ((ri.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
+ ComponentName cn = new ComponentName(
+ ri.activityInfo.packageName, ri.activityInfo.name);
+ if (singleUserReceivers == null) {
+ singleUserReceivers = new HashSet<ComponentName>();
+ }
+ if (!singleUserReceivers.contains(cn)) {
+ singleUserReceivers.add(cn);
+ receivers.add(ri);
+ }
+ } else {
+ receivers.add(ri);
+ }
+ }
+ }
+ }
+ if (receivers != null && broadcastAllowList != null) {
+ for (int i = receivers.size() - 1; i >= 0; i--) {
+ final int receiverAppId = UserHandle.getAppId(
+ receivers.get(i).activityInfo.applicationInfo.uid);
+ if (receiverAppId >= Process.FIRST_APPLICATION_UID
+ && Arrays.binarySearch(broadcastAllowList, receiverAppId) < 0) {
+ receivers.remove(i);
+ }
+ }
+ }
+ return receivers;
+ }
+
+ private void checkBroadcastFromSystem(Intent intent, ProcessRecord callerApp,
+ String callerPackage, int callingUid, boolean isProtectedBroadcast, List receivers) {
+ if ((intent.getFlags() & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
+ // Don't yell about broadcasts sent via shell
+ return;
+ }
+
+ final String action = intent.getAction();
+ if (isProtectedBroadcast
+ || Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
+ || Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(action)
+ || Intent.ACTION_MEDIA_BUTTON.equals(action)
+ || Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action)
+ || Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(action)
+ || Intent.ACTION_MASTER_CLEAR.equals(action)
+ || Intent.ACTION_FACTORY_RESET.equals(action)
+ || AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
+ || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)
+ || TelephonyManager.ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE.equals(action)
+ || SuggestionSpan.ACTION_SUGGESTION_PICKED.equals(action)
+ || AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION.equals(action)
+ || AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION.equals(action)) {
+ // Broadcast is either protected, or it's a public action that
+ // we've relaxed, so it's fine for system internals to send.
+ return;
+ }
+
+ // This broadcast may be a problem... but there are often system components that
+ // want to send an internal broadcast to themselves, which is annoying to have to
+ // explicitly list each action as a protected broadcast, so we will check for that
+ // one safe case and allow it: an explicit broadcast, only being received by something
+ // that has protected itself.
+ if (intent.getPackage() != null || intent.getComponent() != null) {
+ if (receivers == null || receivers.size() == 0) {
+ // Intent is explicit and there's no receivers.
+ // This happens, e.g. , when a system component sends a broadcast to
+ // its own runtime receiver, and there's no manifest receivers for it,
+ // because this method is called twice for each broadcast,
+ // for runtime receivers and manifest receivers and the later check would find
+ // no receivers.
+ return;
+ }
+ boolean allProtected = true;
+ for (int i = receivers.size() - 1; i >= 0; i--) {
+ Object target = receivers.get(i);
+ if (target instanceof ResolveInfo) {
+ ResolveInfo ri = (ResolveInfo) target;
+ if (ri.activityInfo.exported && ri.activityInfo.permission == null) {
+ allProtected = false;
+ break;
+ }
+ } else {
+ BroadcastFilter bf = (BroadcastFilter) target;
+ if (bf.exported && bf.requiredPermission == null) {
+ allProtected = false;
+ break;
+ }
+ }
+ }
+ if (allProtected) {
+ // All safe!
+ return;
+ }
+ }
+
+ // The vast majority of broadcasts sent from system internals
+ // should be protected to avoid security holes, so yell loudly
+ // to ensure we examine these cases.
+ if (callerApp != null) {
+ Log.wtf(TAG, "Sending non-protected broadcast " + action
+ + " from system " + callerApp.toShortString() + " pkg " + callerPackage,
+ new Throwable());
+ } else {
+ Log.wtf(TAG, "Sending non-protected broadcast " + action
+ + " from system uid " + UserHandle.formatUid(callingUid)
+ + " pkg " + callerPackage,
+ new Throwable());
+ }
+ }
+
+ // Apply permission policy around the use of specific broadcast options
+ void enforceBroadcastOptionPermissionsInternal(
+ @Nullable Bundle options, int callingUid) {
+ enforceBroadcastOptionPermissionsInternal(BroadcastOptions.fromBundleNullable(options),
+ callingUid);
+ }
+
+ private void enforceBroadcastOptionPermissionsInternal(
+ @Nullable BroadcastOptions options, int callingUid) {
+ if (options != null && callingUid != Process.SYSTEM_UID) {
+ if (options.isAlarmBroadcast()) {
+ if (DEBUG_BROADCAST_LIGHT) {
+ Slog.w(TAG, "Non-system caller " + callingUid
+ + " may not flag broadcast as alarm");
+ }
+ throw new SecurityException(
+ "Non-system callers may not flag broadcasts as alarm");
+ }
+ if (options.isInteractive()) {
+ mService.enforceCallingPermission(
+ android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE,
+ "setInteractive");
+ }
+ }
+ }
+
+ void startBroadcastObservers() {
+ mBroadcastQueue.start(mContext.getContentResolver());
+ }
+
+ void removeStickyBroadcasts(int userId) {
+ synchronized (mStickyBroadcasts) {
+ mStickyBroadcasts.remove(userId);
+ }
+ }
+
+ @NeverCompile
+ void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+ int opti, boolean dumpAll, String dumpPackage) {
+ boolean dumpConstants = true;
+ boolean dumpHistory = true;
+ boolean needSep = false;
+ boolean onlyHistory = false;
+ boolean printedAnything = false;
+ boolean onlyReceivers = false;
+ int filteredUid = Process.INVALID_UID;
+
+ if ("history".equals(dumpPackage)) {
+ if (opti < args.length && "-s".equals(args[opti])) {
+ dumpAll = false;
+ }
+ onlyHistory = true;
+ dumpPackage = null;
+ }
+ if ("receivers".equals(dumpPackage)) {
+ onlyReceivers = true;
+ dumpPackage = null;
+ if (opti + 2 <= args.length) {
+ for (int i = opti; i < args.length; i++) {
+ String arg = args[i];
+ switch (arg) {
+ case "--uid":
+ filteredUid = getIntArg(pw, args, ++i, Process.INVALID_UID);
+ if (filteredUid == Process.INVALID_UID) {
+ return;
+ }
+ break;
+ default:
+ pw.printf("Invalid argument at index %d: %s\n", i, arg);
+ return;
+ }
+ }
+ }
+ }
+ if (DEBUG_BROADCAST) {
+ Slogf.d(TAG_BROADCAST, "dumpBroadcastsLocked(): dumpPackage=%s, onlyHistory=%b, "
+ + "onlyReceivers=%b, filteredUid=%d", dumpPackage, onlyHistory,
+ onlyReceivers, filteredUid);
+ }
+
+ pw.println("ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts)");
+ if (!onlyHistory && dumpAll) {
+ if (mRegisteredReceivers.size() > 0) {
+ boolean printed = false;
+ Iterator it = mRegisteredReceivers.values().iterator();
+ while (it.hasNext()) {
+ ReceiverList r = (ReceiverList) it.next();
+ if (dumpPackage != null && (r.app == null
+ || !dumpPackage.equals(r.app.info.packageName))) {
+ continue;
+ }
+ if (filteredUid != Process.INVALID_UID && filteredUid != r.app.uid) {
+ if (DEBUG_BROADCAST) {
+ Slogf.v(TAG_BROADCAST, "dumpBroadcastsLocked(): skipping receiver whose"
+ + " uid (%d) is not %d: %s", r.app.uid, filteredUid, r.app);
+ }
+ continue;
+ }
+ if (!printed) {
+ pw.println(" Registered Receivers:");
+ needSep = true;
+ printed = true;
+ printedAnything = true;
+ }
+ pw.print(" * "); pw.println(r);
+ r.dump(pw, " ");
+ }
+ } else {
+ if (onlyReceivers) {
+ pw.println(" (no registered receivers)");
+ }
+ }
+
+ if (!onlyReceivers) {
+ if (mReceiverResolver.dump(pw, needSep
+ ? "\n Receiver Resolver Table:" : " Receiver Resolver Table:",
+ " ", dumpPackage, false, false)) {
+ needSep = true;
+ printedAnything = true;
+ }
+ }
+ }
+
+ if (!onlyReceivers) {
+ needSep = mBroadcastQueue.dumpLocked(fd, pw, args, opti,
+ dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
+ printedAnything |= needSep;
+ }
+
+ needSep = true;
+
+ synchronized (mStickyBroadcasts) {
+ if (!onlyHistory && !onlyReceivers && mStickyBroadcasts != null
+ && dumpPackage == null) {
+ for (int user = 0; user < mStickyBroadcasts.size(); user++) {
+ if (needSep) {
+ pw.println();
+ }
+ needSep = true;
+ printedAnything = true;
+ pw.print(" Sticky broadcasts for user ");
+ pw.print(mStickyBroadcasts.keyAt(user));
+ pw.println(":");
+ StringBuilder sb = new StringBuilder(128);
+ for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
+ : mStickyBroadcasts.valueAt(user).entrySet()) {
+ pw.print(" * Sticky action ");
+ pw.print(ent.getKey());
+ if (dumpAll) {
+ pw.println(":");
+ ArrayList<StickyBroadcast> broadcasts = ent.getValue();
+ final int N = broadcasts.size();
+ for (int i = 0; i < N; i++) {
+ final Intent intent = broadcasts.get(i).intent;
+ final boolean deferUntilActive = broadcasts.get(i).deferUntilActive;
+ sb.setLength(0);
+ sb.append(" Intent: ");
+ intent.toShortString(sb, false, true, false, false);
+ pw.print(sb);
+ if (deferUntilActive) {
+ pw.print(" [D]");
+ }
+ pw.println();
+ pw.print(" originalCallingUid: ");
+ pw.println(broadcasts.get(i).originalCallingUid);
+ pw.println();
+ Bundle bundle = intent.getExtras();
+ if (bundle != null) {
+ pw.print(" extras: ");
+ pw.println(bundle);
+ }
+ }
+ } else {
+ pw.println("");
+ }
+ }
+ }
+ }
+ }
+
+ if (!onlyHistory && !onlyReceivers && dumpAll) {
+ pw.println();
+ pw.println(" Queue " + mBroadcastQueue.toString() + ": "
+ + mBroadcastQueue.describeStateLocked());
+ pw.println(" mHandler:");
+ mService.mHandler.dump(new PrintWriterPrinter(pw), " ");
+ needSep = true;
+ printedAnything = true;
+ }
+
+ if (!printedAnything) {
+ pw.println(" (nothing)");
+ }
+ }
+
+ /**
+ * Gets an {@code int} argument from the given {@code index} on {@code args}, logging an error
+ * message on {@code pw} when it cannot be parsed.
+ *
+ * Returns {@code int} argument or {@code invalidValue} if it could not be parsed.
+ */
+ private static int getIntArg(PrintWriter pw, String[] args, int index, int invalidValue) {
+ if (index > args.length) {
+ pw.println("Missing argument");
+ return invalidValue;
+ }
+ String arg = args[index];
+ try {
+ return Integer.parseInt(arg);
+ } catch (Exception e) {
+ pw.printf("Non-numeric argument at index %d: %s\n", index, arg);
+ return invalidValue;
+ }
+ }
+
+ @NeverCompile
+ void dumpBroadcastStatsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+ int opti, boolean dumpAll, String dumpPackage) {
+ if (mCurBroadcastStats == null) {
+ return;
+ }
+
+ pw.println("ACTIVITY MANAGER BROADCAST STATS STATE (dumpsys activity broadcast-stats)");
+ final long now = SystemClock.elapsedRealtime();
+ if (mLastBroadcastStats != null) {
+ pw.print(" Last stats (from ");
+ TimeUtils.formatDuration(mLastBroadcastStats.mStartRealtime, now, pw);
+ pw.print(" to ");
+ TimeUtils.formatDuration(mLastBroadcastStats.mEndRealtime, now, pw);
+ pw.print(", ");
+ TimeUtils.formatDuration(mLastBroadcastStats.mEndUptime
+ - mLastBroadcastStats.mStartUptime, pw);
+ pw.println(" uptime):");
+ if (!mLastBroadcastStats.dumpStats(pw, " ", dumpPackage)) {
+ pw.println(" (nothing)");
+ }
+ pw.println();
+ }
+ pw.print(" Current stats (from ");
+ TimeUtils.formatDuration(mCurBroadcastStats.mStartRealtime, now, pw);
+ pw.print(" to now, ");
+ TimeUtils.formatDuration(SystemClock.uptimeMillis()
+ - mCurBroadcastStats.mStartUptime, pw);
+ pw.println(" uptime):");
+ if (!mCurBroadcastStats.dumpStats(pw, " ", dumpPackage)) {
+ pw.println(" (nothing)");
+ }
+ }
+
+ @NeverCompile
+ void dumpBroadcastStatsCheckinLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+ int opti, boolean fullCheckin, String dumpPackage) {
+ if (mCurBroadcastStats == null) {
+ return;
+ }
+
+ if (mLastBroadcastStats != null) {
+ mLastBroadcastStats.dumpCheckinStats(pw, dumpPackage);
+ if (fullCheckin) {
+ mLastBroadcastStats = null;
+ return;
+ }
+ }
+ mCurBroadcastStats.dumpCheckinStats(pw, dumpPackage);
+ if (fullCheckin) {
+ mCurBroadcastStats = null;
+ }
+ }
+
+ void writeBroadcastsToProtoLocked(ProtoOutputStream proto) {
+ if (mRegisteredReceivers.size() > 0) {
+ Iterator it = mRegisteredReceivers.values().iterator();
+ while (it.hasNext()) {
+ ReceiverList r = (ReceiverList) it.next();
+ r.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_LIST);
+ }
+ }
+ mReceiverResolver.dumpDebug(proto,
+ ActivityManagerServiceDumpBroadcastsProto.RECEIVER_RESOLVER);
+ mBroadcastQueue.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
+ synchronized (mStickyBroadcasts) {
+ for (int user = 0; user < mStickyBroadcasts.size(); user++) {
+ long token = proto.start(
+ ActivityManagerServiceDumpBroadcastsProto.STICKY_BROADCASTS);
+ proto.write(StickyBroadcastProto.USER, mStickyBroadcasts.keyAt(user));
+ for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
+ : mStickyBroadcasts.valueAt(user).entrySet()) {
+ long actionToken = proto.start(StickyBroadcastProto.ACTIONS);
+ proto.write(StickyBroadcastProto.StickyAction.NAME, ent.getKey());
+ for (StickyBroadcast broadcast : ent.getValue()) {
+ broadcast.intent.dumpDebug(proto, StickyBroadcastProto.StickyAction.INTENTS,
+ false, true, true, false);
+ }
+ proto.end(actionToken);
+ }
+ proto.end(token);
+ }
+ }
+
+ long handlerToken = proto.start(ActivityManagerServiceDumpBroadcastsProto.HANDLER);
+ proto.write(ActivityManagerServiceDumpBroadcastsProto.MainHandler.HANDLER,
+ mService.mHandler.toString());
+ mService.mHandler.getLooper().dumpDebug(proto,
+ ActivityManagerServiceDumpBroadcastsProto.MainHandler.LOOPER);
+ proto.end(handlerToken);
+ }
+
+ @VisibleForTesting
+ static final class StickyBroadcast {
+ public Intent intent;
+ public boolean deferUntilActive;
+ public int originalCallingUid;
+ /** The snapshot process state of the app who sent this broadcast */
+ public int originalCallingAppProcessState;
+ public String resolvedDataType;
+
+ public static StickyBroadcast create(Intent intent, boolean deferUntilActive,
+ int originalCallingUid, int originalCallingAppProcessState,
+ String resolvedDataType) {
+ final StickyBroadcast b = new StickyBroadcast();
+ b.intent = intent;
+ b.deferUntilActive = deferUntilActive;
+ b.originalCallingUid = originalCallingUid;
+ b.originalCallingAppProcessState = originalCallingAppProcessState;
+ b.resolvedDataType = resolvedDataType;
+ return b;
+ }
+
+ @Override
+ public String toString() {
+ return "{intent=" + intent + ", defer=" + deferUntilActive + ", originalCallingUid="
+ + originalCallingUid + ", originalCallingAppProcessState="
+ + originalCallingAppProcessState + ", type=" + resolvedDataType + "}";
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java
index 7d26004..5488446 100644
--- a/services/core/java/com/android/server/display/BrightnessSetting.java
+++ b/services/core/java/com/android/server/display/BrightnessSetting.java
@@ -150,6 +150,13 @@
}
/**
+ * Flush the brightness update that has been made to the persistent data store.
+ */
+ public void saveIfNeeded() {
+ mPersistentDataStore.saveIfNeeded();
+ }
+
+ /**
* @return The brightness for the default display in nits. Used when the underlying display
* device has changed but we want to persist the nit value.
*/
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 55a6ce7..187caba 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1181,7 +1181,10 @@
private DisplayInfo getDisplayInfoForFrameRateOverride(DisplayEventReceiver.FrameRateOverride[]
frameRateOverrides, DisplayInfo info, int callingUid) {
+ // Start with the display frame rate
float frameRateHz = info.renderFrameRate;
+
+ // If the app has a specific override, use that instead
for (DisplayEventReceiver.FrameRateOverride frameRateOverride : frameRateOverrides) {
if (frameRateOverride.uid == callingUid) {
frameRateHz = frameRateOverride.frameRateHz;
@@ -1200,18 +1203,21 @@
DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE, callingUid);
// Override the refresh rate only if it is a divisor of the current
- // refresh rate. This calculation needs to be in sync with the native code
+ // vsync rate. This calculation needs to be in sync with the native code
// in RefreshRateSelector::getFrameRateDivisor
Display.Mode currentMode = info.getMode();
- float numPeriods = currentMode.getRefreshRate() / frameRateHz;
+ float vsyncRate = currentMode.getVsyncRate();
+ float numPeriods = vsyncRate / frameRateHz;
float numPeriodsRound = Math.round(numPeriods);
if (Math.abs(numPeriods - numPeriodsRound) > THRESHOLD_FOR_REFRESH_RATES_DIVISORS) {
return info;
}
- frameRateHz = currentMode.getRefreshRate() / numPeriodsRound;
+ frameRateHz = vsyncRate / numPeriodsRound;
DisplayInfo overriddenInfo = new DisplayInfo();
overriddenInfo.copyFrom(info);
+
+ // If there is a mode that matches the override, use that one
for (Display.Mode mode : info.supportedModes) {
if (!mode.equalsExceptRefreshRate(currentMode)) {
continue;
@@ -1231,8 +1237,9 @@
return overriddenInfo;
}
}
-
overriddenInfo.refreshRateOverride = frameRateHz;
+
+ // Create a fake mode for app compat
if (!displayModeReturnsPhysicalRefreshRate) {
overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
info.supportedModes.length + 1);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index ab79713..a887f6d 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2484,6 +2484,11 @@
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
== Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+ if (screenBrightnessModeSetting == Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) {
+ // In manual mode, all brightness changes should be saved immediately.
+ mDisplayBrightnessController.saveBrightnessIfNeeded();
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index e157b05..72a91d5 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -339,6 +339,13 @@
}
/**
+ * Flush the brightness update that has been made to the persistent data store.
+ */
+ public void saveBrightnessIfNeeded() {
+ mBrightnessSetting.saveIfNeeded();
+ }
+
+ /**
* Sets the current screen brightness, and notifies the BrightnessSetting about the change.
*/
public void updateScreenBrightnessSetting(float brightnessValue, float maxBrightness) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index f34b4e9..7ce9ee6 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3037,6 +3037,7 @@
intent.putExtra("input_method_id", id);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
+ bindingController.unbindCurrentMethod();
unbindCurrentClientLocked(UnbindReason.SWITCH_IME, userId);
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index ee0159d..4665a72 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -3065,10 +3065,9 @@
case DumpState.DUMP_PREFERRED_XML:
{
pw.flush();
- FileOutputStream fout = new FileOutputStream(fd);
- BufferedOutputStream str = new BufferedOutputStream(fout);
TypedXmlSerializer serializer = Xml.newFastSerializer();
- try {
+ try (BufferedOutputStream str =
+ new BufferedOutputStream(new FileOutputStream(fd))) {
serializer.setOutput(str, StandardCharsets.UTF_8.name());
serializer.startDocument(null, true);
serializer.setFeature(
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 5105fd3..ada6659b 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2231,8 +2231,9 @@
// by apexd to be more accurate.
installRequest.setScannedPackageSettingFirstInstallTimeFromReplaced(
deletedPkgSetting, allUsers);
- installRequest.setScannedPackageSettingLastUpdateTime(
- System.currentTimeMillis());
+ long currentTime = System.currentTimeMillis();
+ installRequest.setScannedPackageSettingLastUpdateTime(currentTime);
+ installRequest.setScannedPackageSettingFirstInstallTime(currentTime);
installRequest.getRemovedInfo().mBroadcastAllowList =
mPm.mAppsFilter.getVisibilityAllowList(mPm.snapshotComputer(),
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index dd2583a0d..ae7749b 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -58,6 +58,7 @@
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.PackageUserStateInternal;
import java.io.File;
import java.util.ArrayList;
@@ -867,6 +868,14 @@
mScanResult.mPkgSetting.setLastUpdateTime(lastUpdateTim);
}
+ public void setScannedPackageSettingFirstInstallTime(long firstInstallTime) {
+ assertScanResultExists();
+ PackageUserStateInternal userState = mScanResult.mPkgSetting.getUserStates().get(mUserId);
+ if (userState != null && userState.getFirstInstallTimeMillis() == 0) {
+ mScanResult.mPkgSetting.setFirstInstallTime(firstInstallTime, mUserId);
+ }
+ }
+
public void setRemovedAppId(int appId) {
if (mRemovedInfo != null) {
mRemovedInfo.mUid = appId;
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 61fddba..0802e9e 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -443,17 +443,16 @@
// Take care of first install / last update times.
final long scanFileTime = getLastModifiedTime(parsedPackage);
- final long existingFirstInstallTime = userId == UserHandle.USER_ALL
- ? PackageStateUtils.getEarliestFirstInstallTime(pkgSetting.getUserStates())
- : pkgSetting.readUserState(userId).getFirstInstallTimeMillis();
+ final long earliestFirstInstallTime =
+ PackageStateUtils.getEarliestFirstInstallTime((pkgSetting.getUserStates()));
if (currentTime != 0) {
- if (existingFirstInstallTime == 0) {
+ if (earliestFirstInstallTime == 0) {
pkgSetting.setFirstInstallTime(currentTime, userId)
.setLastUpdateTime(currentTime);
} else if ((scanFlags & SCAN_UPDATE_TIME) != 0) {
pkgSetting.setLastUpdateTime(currentTime);
}
- } else if (existingFirstInstallTime == 0) {
+ } else if (earliestFirstInstallTime == 0) {
// We need *something*. Take time stamp of the file.
pkgSetting.setFirstInstallTime(scanFileTime, userId)
.setLastUpdateTime(scanFileTime);
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 00582bf..045d4db 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -282,6 +282,12 @@
for (int j = 0; j < idSize; j++) {
ShortcutService.writeTagValue(out, TAG_PIN, ids.valueAt(j));
}
+ if (ShortcutService.DEBUG_REBOOT) {
+ Slog.d(TAG, "Persist shortcut ids pinned by "
+ + getPackageName() + " from "
+ + up.userId + "@" + up.packageName + " ids=["
+ + String.join(", ", ids) + "]");
+ }
out.endTag(null, TAG_PACKAGE);
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 84674b2..60056eb 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1850,9 +1850,17 @@
}
getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
+ if (ShortcutService.DEBUG_REBOOT) {
+ Slog.d(TAG, "Persisting shortcuts from "
+ + getOwnerUserId() + "@" + getPackageName());
+ }
for (int j = 0; j < size; j++) {
+ final ShortcutInfo si = mShortcuts.valueAt(j);
saveShortcut(
- out, mShortcuts.valueAt(j), forBackup, getPackageInfo().isBackupAllowed());
+ out, si, forBackup, getPackageInfo().isBackupAllowed());
+ if (ShortcutService.DEBUG_REBOOT) {
+ Slog.d(TAG, si.toSimpleString());
+ }
}
if (!forBackup) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 021f7aa..25468fa 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -172,7 +172,7 @@
static final boolean DEBUG = false; // STOPSHIP if true
static final boolean DEBUG_LOAD = false; // STOPSHIP if true
static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
- static final boolean DEBUG_REBOOT = true;
+ static final boolean DEBUG_REBOOT = Build.IS_DEBUGGABLE;
@VisibleForTesting
static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 2b639fa..4a2d367 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1372,6 +1372,10 @@
}
if (isHeadlessSystemUserMode()) {
+ if (mContext.getResources()
+ .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser)) {
+ return UserHandle.USER_SYSTEM;
+ }
// Return the previous foreground user, if there is one.
final int previousUser = getPreviousFullUserToEnterForeground();
if (previousUser != UserHandle.USER_NULL) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 530c03f..9d91d3d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8567,7 +8567,8 @@
resolvedConfig,
mOptOutEdgeToEdge,
hasFixedRotationTransform(),
- getCompatDisplayInsets() != null);
+ getCompatDisplayInsets() != null,
+ task);
mResolveConfigHint.resetTmpOverrides();
logAppCompatState();
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 9be3f43..670a61d 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -195,12 +195,14 @@
* screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation.
* All overrides to those fields should be in this method.
*
+ * Task is only needed for split-screen to apply an offset special handling.
+ *
* TODO: Consider integrate this with computeConfigByResolveHint()
*/
static void applySizeOverrideIfNeeded(DisplayContent displayContent, ApplicationInfo appInfo,
Configuration newParentConfiguration, Configuration inOutConfig,
boolean optsOutEdgeToEdge, boolean hasFixedRotationTransform,
- boolean hasCompatDisplayInsets) {
+ boolean hasCompatDisplayInsets, Task task) {
if (displayContent == null) {
return;
}
@@ -223,13 +225,16 @@
}
if (!optsOutEdgeToEdge && (!useOverrideInsetsForConfig
|| hasCompatDisplayInsets
- || isFloating
|| rotation == ROTATION_UNDEFINED)) {
// If the insets configuration decoupled logic is not enabled for the app, or the app
// already has a compat override, or the context doesn't contain enough info to
// calculate the override, skip the override.
return;
}
+ if (isFloating) {
+ // Floating window won't have any insets affect configuration. Skip the override.
+ return;
+ }
// Make sure the orientation related fields will be updated by the override insets, because
// fixed rotation has assigned the fields from display's configuration.
if (hasFixedRotationTransform) {
@@ -255,17 +260,17 @@
inOutConfig.windowConfiguration.setAppBounds(
newParentConfiguration.windowConfiguration.getBounds());
outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- if (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
- final DisplayPolicy.DecorInsets.Info decor =
- displayContent.getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh);
- if (outAppBounds.contains(decor.mOverrideNonDecorFrame)) {
- outAppBounds.intersect(decor.mOverrideNonDecorFrame);
+ if (task != null) {
+ task = task.getCreatedByOrganizerTask();
+ if (task != null && (task.mOffsetYForInsets != 0 || task.mOffsetXForInsets != 0)) {
+ outAppBounds.offset(task.mOffsetXForInsets, task.mOffsetYForInsets);
}
- } else {
- // TODO(b/358509380): Handle other windowing mode like split screen and freeform
- // cases correctly.
- outAppBounds.inset(displayContent.getDisplayPolicy()
- .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets);
+ }
+ final DisplayPolicy.DecorInsets.Info decor =
+ displayContent.getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh);
+ outAppBounds.intersectUnchecked(decor.mOverrideNonDecorFrame);
+ if (task != null && (task.mOffsetYForInsets != 0 || task.mOffsetXForInsets != 0)) {
+ outAppBounds.offset(-task.mOffsetXForInsets, -task.mOffsetYForInsets);
}
}
float density = inOutConfig.densityDpi;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 92813a8..08b1e37 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -499,6 +499,13 @@
*/
boolean mIsTrimmableFromRecents;
+ /**
+ * Bounds offset should be applied when calculating compatible configuration for apps targeting
+ * SDK level 34 or before.
+ */
+ int mOffsetXForInsets;
+ int mOffsetYForInsets;
+
private final AnimatingActivityRegistry mAnimatingActivityRegistry =
new AnimatingActivityRegistry();
@@ -5874,6 +5881,10 @@
super.dumpInner(prefix, pw, dumpAll, dumpPackage);
if (mCreatedByOrganizer) {
pw.println(prefix + " mCreatedByOrganizer=true");
+ if (mOffsetXForInsets != 0 || mOffsetYForInsets != 0) {
+ pw.println(prefix + " mOffsetXForInsets=" + mOffsetXForInsets
+ + " mOffsetYForInsets=" + mOffsetYForInsets);
+ }
}
if (mLastNonFullscreenBounds != null) {
pw.print(prefix); pw.print(" mLastNonFullscreenBounds=");
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 9ae881b..a980b77 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -2997,12 +2998,18 @@
// Make sure display isn't a part of the transition already - needed for legacy transitions.
if (mDisplayContent.inTransition()) return false;
- if (!ActivityTaskManagerService.isPip2ExperimentEnabled()) {
- // Screenshots are turned off when PiP is undergoing changes.
- return !inPinnedWindowingMode() && getParent() != null
- && !getParent().inPinnedWindowingMode();
- }
- return true;
+ // Screenshots are turned off when PiP is undergoing changes.
+ return ActivityTaskManagerService.isPip2ExperimentEnabled() || !isPipChange();
+ }
+
+ /** Returns true if WC is pinned and undergoing changes. */
+ private boolean isPipChange() {
+ final boolean isExitingPip = this.asTaskFragment() != null
+ && mTransitionController.getWindowingModeAtStart(this) == WINDOWING_MODE_PINNED
+ && !inPinnedWindowingMode();
+
+ return isExitingPip || inPinnedWindowingMode()
+ || (getParent() != null && getParent().inPinnedWindowingMode());
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 9d40b16..e1e64ee 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -18,9 +18,12 @@
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.app.ActivityManager.isStartResultSuccessful;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
+import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;
@@ -818,6 +821,31 @@
final Configuration c =
new Configuration(container.getRequestedOverrideConfiguration());
c.setTo(change.getConfiguration(), configMask, windowMask);
+ if (container.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
+ && (change.getConfigSetMask() & ActivityInfo.CONFIG_SCREEN_SIZE) != 0) {
+ // Special handling for split screen window got offset. The insets calculation
+ // for configuration should be stable regardless of the offset. Set offset to
+ // the task level to be applied when calculate compat override for apps
+ // targeting SDK level 34 or before.
+ final Task task = container.asTask();
+ if (task != null) {
+ if (c.screenWidthDp != SCREEN_WIDTH_DP_UNDEFINED
+ && c.screenHeightDp != SCREEN_HEIGHT_DP_UNDEFINED) {
+ final Rect oldBounds = container.getRequestedOverrideBounds();
+ final Rect newBounds =
+ change.getConfiguration().windowConfiguration.getBounds();
+ if (oldBounds.width() == newBounds.width()
+ && oldBounds.height() == newBounds.height()) {
+ task.mOffsetXForInsets = oldBounds.left - newBounds.left;
+ task.mOffsetYForInsets = oldBounds.top - newBounds.top;
+ } else {
+ task.mOffsetXForInsets = task.mOffsetYForInsets = 0;
+ }
+ } else {
+ task.mOffsetXForInsets = task.mOffsetYForInsets = 0;
+ }
+ }
+ }
container.onRequestedOverrideConfigurationChanged(c);
}
effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 2bae0a8..d2aebdee 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1689,7 +1689,8 @@
resolvedConfig,
false /* optsOutEdgeToEdge */,
false /* hasFixedRotationTransform */,
- false /* hasCompatDisplayInsets */);
+ false /* hasCompatDisplayInsets */,
+ null /* task */);
}
void dispatchConfiguration(@NonNull Configuration config) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4568f2e..eed0cf7 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3002,7 +3002,8 @@
resolvedConfig,
(mAttrs.privateFlags & PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE) != 0,
false /* hasFixedRotationTransform */,
- false /* hasCompatDisplayInsets */);
+ false /* hasCompatDisplayInsets */,
+ null /* task */);
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowTracingDataSource.java b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
index 6984f0d..2c5a453 100644
--- a/services/core/java/com/android/server/wm/WindowTracingDataSource.java
+++ b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
@@ -33,6 +33,7 @@
import android.util.proto.ProtoInputStream;
import java.io.IOException;
+import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
@@ -76,15 +77,15 @@
private static final String TAG = "WindowTracingDataSource";
@NonNull
- private final Consumer<Config> mOnStartCallback;
+ private final WeakReference<Consumer<Config>> mOnStartCallback;
@NonNull
- private final Consumer<Config> mOnStopCallback;
+ private final WeakReference<Consumer<Config>> mOnStopCallback;
public WindowTracingDataSource(@NonNull Consumer<Config> onStart,
@NonNull Consumer<Config> onStop) {
super(DATA_SOURCE_NAME);
- mOnStartCallback = onStart;
- mOnStopCallback = onStop;
+ mOnStartCallback = new WeakReference(onStart);
+ mOnStopCallback = new WeakReference(onStop);
Producer.init(InitArguments.DEFAULTS);
DataSourceParams params =
@@ -102,12 +103,18 @@
return new Instance(this, instanceIndex, config != null ? config : CONFIG_DEFAULT) {
@Override
protected void onStart(StartCallbackArguments args) {
- mOnStartCallback.accept(mConfig);
+ Consumer<Config> callback = mOnStartCallback.get();
+ if (callback != null) {
+ callback.accept(mConfig);
+ }
}
@Override
protected void onStop(StopCallbackArguments args) {
- mOnStopCallback.accept(mConfig);
+ Consumer<Config> callback = mOnStopCallback.get();
+ if (callback != null) {
+ callback.accept(mConfig);
+ }
}
};
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d5013517..1290fb7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -23501,6 +23501,8 @@
MANAGE_DEVICE_POLICY_ACROSS_USERS);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WIPE_DATA,
MANAGE_DEVICE_POLICY_ACROSS_USERS);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_CONTENT_PROTECTION,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS);
// These permissions may grant access to user data and therefore must be protected with
// MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL for cross-user calls.
@@ -24136,15 +24138,13 @@
@Override
public @ContentProtectionPolicy int getContentProtectionPolicy(
- ComponentName who, String callerPackageName) {
+ ComponentName who, String callerPackageName, int userId) {
if (!android.view.contentprotection.flags.Flags.manageDevicePolicyEnabled()) {
return CONTENT_PROTECTION_DISABLED;
}
CallerIdentity caller = getCallerIdentity(who, callerPackageName);
- int userId = caller.getUserId();
enforceCanQuery(MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, callerPackageName, userId);
-
Integer policy =
mDevicePolicyEngine.getResolvedPolicy(PolicyDefinition.CONTENT_PROTECTION, userId);
if (policy == null) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index bbf2ecb..026fcc4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -2080,6 +2080,31 @@
}
/**
+ * Tests that the DisplayInfo is updated correctly with a render frame rate even if it not
+ * a divisor of the peak refresh rate.
+ */
+ @Test
+ public void testDisplayInfoRenderFrameRateNonPeakDivisor() {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+ new float[]{120f}, new float[]{240f});
+ int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+ displayDevice);
+ DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(120f, displayInfo.getRefreshRate(), 0.01f);
+
+ updateRenderFrameRate(displayManager, displayDevice, 80f);
+ displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(80f, displayInfo.getRefreshRate(), 0.01f);
+ }
+
+ /**
* Tests that the mode reflects the render frame rate is in compat mode
*/
@Test
@@ -3348,13 +3373,26 @@
}
private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
-
float[] refreshRates) {
return createFakeDisplayDevice(displayManager, refreshRates, Display.TYPE_UNKNOWN);
}
private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
float[] refreshRates,
+ float[] vsyncRates) {
+ return createFakeDisplayDevice(displayManager, refreshRates, vsyncRates,
+ Display.TYPE_UNKNOWN);
+ }
+
+ private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
+ float[] refreshRates,
+ int displayType) {
+ return createFakeDisplayDevice(displayManager, refreshRates, refreshRates, displayType);
+ }
+
+ private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
+ float[] refreshRates,
+ float[] vsyncRates,
int displayType) {
FakeDisplayDevice displayDevice = new FakeDisplayDevice();
DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
@@ -3363,7 +3401,8 @@
displayDeviceInfo.supportedModes = new Display.Mode[refreshRates.length];
for (int i = 0; i < refreshRates.length; i++) {
displayDeviceInfo.supportedModes[i] =
- new Display.Mode(i + 1, width, height, refreshRates[i]);
+ new Display.Mode(i + 1, width, height, refreshRates[i], vsyncRates[i],
+ new float[0], new int[0]);
}
displayDeviceInfo.modeId = 1;
displayDeviceInfo.type = displayType;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 5840cb9..2166cb7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -2213,6 +2213,20 @@
/* ignoreAnimationLimits= */ anyBoolean());
}
+ @Test
+ public void testManualBrightnessModeSavesBrightness() {
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Initialize
+
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+ advanceTime(1);
+
+ verify(mHolder.brightnessSetting).saveIfNeeded();
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index e610a32..809e13c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -28,8 +28,8 @@
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
-import static android.os.PowerExemptionManager.REASON_DENIED;
import static android.content.ContentResolver.SCHEME_CONTENT;
+import static android.os.PowerExemptionManager.REASON_DENIED;
import static android.os.UserHandle.USER_ALL;
import static android.util.DebugUtils.valueToString;
@@ -68,11 +68,9 @@
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -84,7 +82,6 @@
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
import android.app.ForegroundServiceDelegationOptions;
-import android.app.IApplicationThread;
import android.app.IUidObserver;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -129,7 +126,7 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.sdksandbox.flags.Flags;
import com.android.server.LocalServices;
-import com.android.server.am.ActivityManagerService.StickyBroadcast;
+import com.android.server.am.BroadcastController.StickyBroadcast;
import com.android.server.am.ProcessList.IsolatedUidRange;
import com.android.server.am.ProcessList.IsolatedUidRangeAllocator;
import com.android.server.am.UidObserverController.ChangeRecord;
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 37d87c4e..1cba3c5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -52,6 +52,7 @@
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.multiuser.Flags;
import android.os.PowerManager;
import android.os.ServiceSpecificException;
@@ -152,6 +153,7 @@
private File mTestDir;
private Context mSpiedContext;
+ private Resources mSpyResources;
private @Mock PackageManagerService mMockPms;
private @Mock UserDataPreparer mMockUserDataPreparer;
@@ -193,6 +195,13 @@
doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
mockIsLowRamDevice(false);
+ // Called when getting boot user. config_bootToHeadlessSystemUser is false by default.
+ mSpyResources = spy(mSpiedContext.getResources());
+ when(mSpiedContext.getResources()).thenReturn(mSpyResources);
+ doReturn(false)
+ .when(mSpyResources)
+ .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser);
+
// Must construct UserManagerService in the UiThread
mTestDir = new File(mRealContext.getDataDir(), "umstest");
mTestDir.mkdirs();
@@ -849,6 +858,16 @@
USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
}
+ @Test
+ public void testGetBootUser_enableBootToHeadlessSystemUser() {
+ setSystemUserHeadless(true);
+ doReturn(true)
+ .when(mSpyResources)
+ .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser);
+
+ assertThat(mUms.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
+ }
+
/**
* Returns true if the user's XML file has Default restrictions
* @param userId Id of the user.
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 8811e00..753cb1f 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -16,12 +16,17 @@
package com.android.server.wm.flicker.helpers
+import android.content.Context
+import android.graphics.Insets
import android.graphics.Rect
+import android.graphics.Region
import android.platform.uiautomator_helpers.DeviceHelpers
import android.tools.device.apphelpers.IStandardAppHelper
import android.tools.helpers.SYSTEMUI_PACKAGE
import android.tools.traces.parsers.WindowManagerStateHelper
import android.tools.traces.wm.WindowingMode
+import android.view.WindowInsets
+import android.view.WindowManager
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiDevice
@@ -70,9 +75,7 @@
// Start dragging a little under the top to prevent dragging the notification shade.
val startY = 10
- val displayRect =
- wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
- ?: throw IllegalStateException("Default display is null")
+ val displayRect = getDisplayRect(wmHelper)
// The position we want to drag to
val endY = displayRect.centerY() / 2
@@ -81,18 +84,61 @@
device.drag(startX, startY, startX, endY, 100)
}
+ private fun getMaximizeButtonForTheApp(caption: UiObject2?): UiObject2 {
+ return caption
+ ?.children
+ ?.find { it.resourceName.endsWith(MAXIMIZE_BUTTON_VIEW) }
+ ?.children
+ ?.get(0)
+ ?: error("Unable to find resource $MAXIMIZE_BUTTON_VIEW\n")
+ }
+
/** Click maximise button on the app header for the given app. */
fun maximiseDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) {
val caption = getCaptionForTheApp(wmHelper, device)
- val maximizeButton =
- caption
- ?.children
- ?.find { it.resourceName.endsWith(MAXIMIZE_BUTTON_VIEW) }
- ?.children
- ?.get(0)
- maximizeButton?.click()
+ val maximizeButton = getMaximizeButtonForTheApp(caption)
+ maximizeButton.click()
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
}
+
+ /** Open maximize menu and click snap resize button on the app header for the given app. */
+ fun snapResizeDesktopApp(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ context: Context,
+ toLeft: Boolean
+ ) {
+ val caption = getCaptionForTheApp(wmHelper, device)
+ val maximizeButton = getMaximizeButtonForTheApp(caption)
+ maximizeButton?.longClick()
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+
+ val buttonResId = if (toLeft) SNAP_LEFT_BUTTON else SNAP_RIGHT_BUTTON
+ val maximizeMenu = getDesktopAppViewByRes(MAXIMIZE_MENU)
+
+ val snapResizeButton =
+ maximizeMenu
+ ?.wait(Until.findObject(By.res(SYSTEMUI_PACKAGE, buttonResId)), TIMEOUT.toMillis())
+ ?: error("Unable to find object with resource id $buttonResId")
+ snapResizeButton.click()
+
+ val displayRect = getDisplayRect(wmHelper)
+ val insets = getWindowInsets(
+ context, WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
+ displayRect.inset(insets)
+
+ val expectedWidth = displayRect.width() / 2
+ val expectedRect = Rect(displayRect).apply {
+ if (toLeft) right -= expectedWidth else left += expectedWidth
+ }
+
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withSurfaceVisibleRegion(this, Region(expectedRect))
+ .waitForAndVerify()
+ }
+
/** Click close button on the app header for the given app. */
fun closeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) {
val caption = getCaptionForTheApp(wmHelper, device)
@@ -112,8 +158,7 @@
if (
wmHelper.getWindow(innerHelper)?.windowingMode !=
WindowingMode.WINDOWING_MODE_FREEFORM.value
- )
- error("expected a freeform window with caption but window is not in freeform mode")
+ ) error("expected a freeform window with caption but window is not in freeform mode")
val captions =
device.wait(Until.findObjects(caption), TIMEOUT.toMillis())
?: error("Unable to find view $caption\n")
@@ -156,6 +201,30 @@
.waitForAndVerify()
}
+ /** Drag a window to a snap resize region, found at the left and right edges of the screen. */
+ fun dragToSnapResizeRegion(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ isLeft: Boolean,
+ ) {
+ val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
+ // Set start x-coordinate as center of app header.
+ val startX = windowRect.centerX()
+ val startY = windowRect.top
+
+ val displayRect = getDisplayRect(wmHelper)
+
+ val endX = if (isLeft) displayRect.left else displayRect.right
+ val endY = displayRect.centerY() / 2
+
+ // drag the window to snap resize
+ device.drag(startX, startY, endX, endY, /* steps= */ 100)
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .waitForAndVerify()
+ }
+
private fun getStartCoordinatesForCornerResize(
windowRect: Rect,
corner: Corners
@@ -179,9 +248,7 @@
private fun dragAppWindowToTopDragZone(wmHelper: WindowManagerStateHelper, device: UiDevice) {
val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
- val displayRect =
- wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
- ?: throw IllegalStateException("Default display is null")
+ val displayRect = getDisplayRect(wmHelper)
val startX = windowRect.centerX()
val endX = displayRect.centerX()
@@ -194,7 +261,8 @@
fun enterDesktopModeFromAppHandleMenu(
wmHelper: WindowManagerStateHelper,
- device: UiDevice) {
+ device: UiDevice
+ ) {
val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
val startX = windowRect.centerX()
// Click a little under the top to prevent opening the notification shade.
@@ -204,7 +272,7 @@
device.click(startX, startY)
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
- val pill = getAppHandlePillForWindow()
+ val pill = getDesktopAppViewByRes(PILL_CONTAINER)
val desktopModeButton =
pill
?.children
@@ -214,10 +282,13 @@
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
}
- private fun getAppHandlePillForWindow(): UiObject2? {
- val pillContainer: BySelector = By.res(SYSTEMUI_PACKAGE, PILL_CONTAINER)
- return DeviceHelpers.waitForObj(pillContainer, TIMEOUT)
- }
+ private fun getDesktopAppViewByRes(viewResId: String): UiObject2? =
+ DeviceHelpers.waitForObj(By.res(SYSTEMUI_PACKAGE, viewResId), TIMEOUT)
+
+ private fun getDisplayRect(wmHelper: WindowManagerStateHelper): Rect =
+ wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
+ ?: throw IllegalStateException("Default display is null")
+
/** Wait for transition to full screen to finish. */
private fun waitForTransitionToFullscreen(wmHelper: WindowManagerStateHelper) {
@@ -228,13 +299,23 @@
.waitForAndVerify()
}
+ private fun getWindowInsets(context: Context, typeMask: Int): Insets {
+ val wm: WindowManager = context.getSystemService(WindowManager::class.java)
+ ?: error("Unable to connect to WindowManager service")
+ val metricInsets = wm.currentWindowMetrics.windowInsets
+ return metricInsets.getInsetsIgnoringVisibility(typeMask)
+ }
+
private companion object {
- val TIMEOUT = Duration.ofSeconds(3)
- val CAPTION = "desktop_mode_caption"
- val MAXIMIZE_BUTTON_VIEW = "maximize_button_view"
- val CLOSE_BUTTON = "close_window"
- val PILL_CONTAINER = "windowing_pill"
- val DESKTOP_MODE_BUTTON = "desktop_button"
+ val TIMEOUT: Duration = Duration.ofSeconds(3)
+ const val CAPTION: String = "desktop_mode_caption"
+ const val MAXIMIZE_BUTTON_VIEW: String = "maximize_button_view"
+ const val MAXIMIZE_MENU: String = "maximize_menu"
+ const val CLOSE_BUTTON: String = "close_window"
+ const val PILL_CONTAINER: String = "windowing_pill"
+ const val DESKTOP_MODE_BUTTON: String = "desktop_button"
+ const val SNAP_LEFT_BUTTON: String = "maximize_menu_snap_left_button"
+ const val SNAP_RIGHT_BUTTON: String = "maximize_menu_snap_right_button"
val caption: BySelector
get() = By.res(SYSTEMUI_PACKAGE, CAPTION)
}
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java
new file mode 100644
index 0000000..9d56a92
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 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.protolog;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.internal.protolog.common.IProtoLogGroup;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test class for {@link ProtoLog}. */
+@SuppressWarnings("ConstantConditions")
+@Presubmit
+@RunWith(JUnit4.class)
+public class ProtoLogTest {
+
+ @Test
+ public void canRunProtoLogInitMultipleTimes() {
+ ProtoLog.init(TEST_GROUP_1);
+ ProtoLog.init(TEST_GROUP_1);
+ ProtoLog.init(TEST_GROUP_2);
+ ProtoLog.init(TEST_GROUP_1, TEST_GROUP_2);
+
+ final var instance = ProtoLog.getSingleInstance();
+ Truth.assertThat(instance.getRegisteredGroups())
+ .containsExactly(TEST_GROUP_1, TEST_GROUP_2);
+ }
+
+ private static final IProtoLogGroup TEST_GROUP_1 = new ProtoLogGroup("TEST_TAG_1", 1);
+ private static final IProtoLogGroup TEST_GROUP_2 = new ProtoLogGroup("TEST_TAG_2", 2);
+
+ private static class ProtoLogGroup implements IProtoLogGroup {
+ private final boolean mEnabled;
+ private volatile boolean mLogToProto;
+ private volatile boolean mLogToLogcat;
+ private final String mTag;
+ private final int mId;
+
+ ProtoLogGroup(String tag, int id) {
+ this(true, true, false, tag, id);
+ }
+
+ ProtoLogGroup(
+ boolean enabled, boolean logToProto, boolean logToLogcat, String tag, int id) {
+ this.mEnabled = enabled;
+ this.mLogToProto = logToProto;
+ this.mLogToLogcat = logToLogcat;
+ this.mTag = tag;
+ this.mId = id;
+ }
+
+ @Override
+ public String name() {
+ return mTag;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ @Override
+ public boolean isLogToProto() {
+ return mLogToProto;
+ }
+
+ @Override
+ public boolean isLogToLogcat() {
+ return mLogToLogcat;
+ }
+
+ @Override
+ public boolean isLogToAny() {
+ return mLogToLogcat || mLogToProto;
+ }
+
+ @Override
+ public String getTag() {
+ return mTag;
+ }
+
+ @Override
+ public void setLogToProto(boolean logToProto) {
+ this.mLogToProto = logToProto;
+ }
+
+ @Override
+ public void setLogToLogcat(boolean logToLogcat) {
+ this.mLogToLogcat = logToLogcat;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+}