diff options
79 files changed, 3427 insertions, 2596 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 965e3c43cca1..ba1dc5677b21 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -4334,11 +4334,13 @@ public class DevicePolicyManager { */ @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 c393a9eeae30..d4e5c9960c2a 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -621,7 +621,7 @@ interface IDevicePolicyManager { 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 a50425e4db91..db3de6261589 100644 --- a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java @@ -40,8 +40,8 @@ public final class ExecuteAppFunctionRequest implements Parcelable { 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 @@ public final class ExecuteAppFunctionRequest implements Parcelable { * * <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 @@ public final class ExecuteAppFunctionRequest implements Parcelable { */ @NonNull public GenericDocument getParameters() { - return mParameters; + return mParameters.getValue(); } /** @@ -152,7 +152,8 @@ public final class ExecuteAppFunctionRequest implements Parcelable { @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 @@ public final class ExecuteAppFunctionRequest implements Parcelable { */ @NonNull public Builder setParameters(@NonNull GenericDocument parameters) { - mParameters = Objects.requireNonNull(parameters); + Objects.requireNonNull(parameters); + mParameters = parameters; return this; } @@ -183,7 +185,8 @@ public final class ExecuteAppFunctionRequest implements Parcelable { @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 872327ddd461..9fb3375a2446 100644 --- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java @@ -41,13 +41,16 @@ public final class ExecuteAppFunctionResponse implements Parcelable { 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 @@ public final class ExecuteAppFunctionResponse implements Parcelable { * <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 @@ public final class ExecuteAppFunctionResponse implements Parcelable { @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 @@ public final class ExecuteAppFunctionResponse implements Parcelable { */ @NonNull public GenericDocument getResultDocument() { - return mResultDocument; + return mResultDocumentWrapper.getValue(); } /** @@ -210,7 +226,7 @@ public final class ExecuteAppFunctionResponse implements Parcelable { @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 @@ public final class ExecuteAppFunctionResponse implements Parcelable { } /** - * 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 @@ public final class ExecuteAppFunctionResponse implements Parcelable { * 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 @@ public final class ExecuteAppFunctionResponse implements Parcelable { @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 000000000000..8c76c8e4b73b --- /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/security/OWNERS b/core/java/android/security/OWNERS index 8bd6c858be7c..c38ee089a5c2 100644 --- a/core/java/android/security/OWNERS +++ b/core/java/android/security/OWNERS @@ -3,6 +3,7 @@ brambonne@google.com eranm@google.com jeffv@google.com +tweek@google.com 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 1734223ec4c1..ad457ce6e18d 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_X; 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.WallpaperManager; 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 @@ public abstract class WallpaperService extends Service { 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 @@ public abstract class WallpaperService extends Service { 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 @@ public abstract class WallpaperService extends Service { } private void prepareToDraw() { + if (mDisableDrawWakeLock) { + return; + } if (mDisplayState == Display.STATE_DOZE) { try { mSession.pokeDrawLock(mWindow); @@ -546,6 +562,8 @@ public abstract class WallpaperService extends Service { 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 15351453a151..815fd1c49a38 100644 --- a/core/java/android/view/PointerIcon.java +++ b/core/java/android/view/PointerIcon.java @@ -556,12 +556,9 @@ public final class PointerIcon implements Parcelable { + "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/com/android/internal/accessibility/TEST_MAPPING b/core/java/com/android/internal/accessibility/TEST_MAPPING index 1c673996b550..b2b304134158 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/res/OWNERS b/core/res/OWNERS index b2b58d557e6f..5293131180ae 100644 --- a/core/res/OWNERS +++ b/core/res/OWNERS @@ -78,6 +78,11 @@ per-file remote_color_resources_res/values/colors.xml = pbdr@google.com 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 000000000000..49fe0815c757 --- /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 09ed65093b39..10d938c71ea4 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/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 434885f1089d..47d52744cd74 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 android.util.Log 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 8ce7837e451f..17869e918d67 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 @@ package com.android.wm.shell.compatui; 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.CompatUIController.CompatUIHintsState; 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 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { 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 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { 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 000000000000..a489c4ffdd94 --- /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/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 273d852df2eb..ffd534b6b814 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 @@ -323,7 +323,7 @@ class DesktopTasksController( 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) @@ -1112,12 +1112,11 @@ class DesktopTasksController( 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/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 e3660fe13bc2..b812c596adba 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.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 com.android.wm.shell.Utils @@ -38,15 +39,20 @@ import org.junit.runners.BlockJUnit4ClassRunner @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/SnapResizeAppWindowWithButton.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt index 06775e1e34a7..685a3ba935d6 100644 --- 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 @@ -23,6 +23,7 @@ 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 @@ -36,13 +37,17 @@ import org.junit.runners.BlockJUnit4ClassRunner @Postsubmit open class SnapResizeAppWindowWithButton @JvmOverloads -constructor(private val toLeft: Boolean = true) { +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 = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + private val testApp = if (isResizable) { + DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + } else { + DesktopModeAppHelper(NonResizeableAppHelper(instrumentation)) + } @Before fun setup() { 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 index 871602f54839..8a4aa6343e4d 100644 --- 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 @@ -23,6 +23,7 @@ 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 @@ -36,13 +37,17 @@ import org.junit.runners.BlockJUnit4ClassRunner @Postsubmit open class SnapResizeAppWindowWithDrag @JvmOverloads -constructor(private val toLeft: Boolean = true) { +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 = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + private val testApp = if (isResizable) { + DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + } else { + DesktopModeAppHelper(NonResizeableAppHelper(instrumentation)) + } @Before fun setup() { 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 2e0af273f2b6..058a26a06f59 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 @@ class DesktopTasksControllerTest : ShellTestCase() { 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 @@ class DesktopTasksControllerTest : ShellTestCase() { 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, freeformTasks[0], toTop = false) + } + + @Test + 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(1, homeTask, toTop = false) - wct.assertReorderAt(2, freeformTasks[0], toTop = false) + wct.assertReorderAt(8, freeformTasks[0], toTop = false) } @Test - fun handleRequest_fullscreenTaskToFreeform_alreadyBeyondLimit_existingAndNewTasksAreMinimized() { + fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_existingAndNewTasksAreMinimized() { assumeTrue(ENABLE_SHELL_TRANSITIONS) val minimizedTask = setUpFreeformTask() @@ -1562,15 +1603,16 @@ class DesktopTasksControllerTest : ShellTestCase() { 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/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 2e98c1fac5eb..f98b29a51604 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 7c0db8d4f59b..0b364ac4a176 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -541,6 +541,16 @@ flag { } 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 @@ flag { 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 0bef05dc00ba..9e292d06613b 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.onGloballyPositioned 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.LowestZIndexContentPicker 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 @@ fun SceneScope.HeadsUpNotificationSpace( .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 @@ fun SceneScope.SnoozeableHeadsUpNotificationSpace( 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 f3994360ea18..055534604734 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 @@ private fun SceneScope.stateForQuickSettingsContent( 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/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt index c5518b029bc1..2ba4bf930f3f 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.keyguardTransitionRepositor 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 @@ class CommunalSceneTransitionInteractorTest : SysuiTestCase() { ) ) } + + /** + * 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 1bc2e24d8c2f..5a6f2be1c9f0 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 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @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 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest } @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 6dbe94bf19c4..a40746859a9a 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 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @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 94fa9b9c631b..e0a53f8817bc 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.testDispatcher 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 kotlinx.coroutines.test.runTest 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 @@ class ModesTileDataInteractorTest : SysuiTestCase() { 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 d472d98c03d4..22913f12330f 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 @@ class QSSceneAdapterImplTest : SysuiTestCase() { 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 63ce67c5eb28..41b5988971d9 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 android.platform.test.annotations.EnabledOnRavenwood 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 @@ class QSSceneAdapterTest : SysuiTestCase() { @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/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt index eac86e55763a..ce9b3beeec9e 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.graphics.drawable.Drawable 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 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 @@ class CommunalSmartspaceControllerTest : SysuiTestCase() { @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 @@ class CommunalSmartspaceControllerTest : SysuiTestCase() { 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 11504aadf743..20d3a7b6b7b5 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 @@ class ZenModeInteractorTest : SysuiTestCase() { 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/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index 040af903a98b..aabfbd11b70e 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 android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS; 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.CLIPBO 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 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv mClipboardUtils = clipboardUtils; mBgExecutor = bgExecutor; - if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) { + if (clipboardSharedTransitions()) { mView.setCallbacks(this); } else { mView.setCallbacks(mClipboardCallbacks); @@ -220,7 +220,7 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv }); 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 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv @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 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv @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 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv 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 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv 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 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv @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 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv @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 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv @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 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv @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 63437525515c..5bbb46d21d8b 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 @@ constructor( // 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 80db5353893f..012c844586bc 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 @@ constructor( 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 e5f3a57de157..4d75d661de49 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 @@ object Flags { // 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 06f77bfe54c6..54964d6ec768 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.BurnInModel 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.shade.domain.interactor.ShadeInteractor 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 @@ constructor( .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, - keyguardTransitionInteractor - .transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE) - .map { it == 1f } - .onStart { emit(false) }, + (hiddenKeyguardStates.map { state -> keyguardTransitionInteractor - .transitionValue(OCCLUDED) + .transitionValue(state) .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 5d81d4f3f9ec..9f9c8e910f20 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.modifiers.padding 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.res.R 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 @@ class QSFragmentCompose @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 @@ constructor( QSComposeFragment.isUnexpectedlyInLegacyMode() viewModel = qsFragmentComposeViewModelFactory.create(lifecycleScope) + qqsMediaHost.init(MediaHierarchyManager.LOCATION_QQS) + qsMediaHost.init(MediaHierarchyManager.LOCATION_QS) setListenerCollections() } @@ -274,7 +283,7 @@ constructor( ) {} 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 9e109e436226..7d52216a4d2e 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 @@ package com.android.systemui.qs.composefragment.viewmodel 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 @@ constructor( _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 dfcf21628c3b..ac6ebe7c87d3 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 @@ interface QSSceneAdapter { 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 @@ interface QSSceneAdapter { */ class UnsquishingQQS(override val squishiness: () -> Float) : State { override val isVisible = true - override val expansion = 0f + override val expansion = { 0f } } /** @@ -181,16 +181,16 @@ interface QSSceneAdapter { */ 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 @@ constructor( 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/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt index 50ea3bbc57e1..448f7c4d7e95 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 @@ constructor( ) { 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 @@ constructor( } 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 3fe3162cf17d..29450a20b1d3 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.data.repository.ProfileTypeRepository 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 @@ constructor( 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 ee1944e0b5cf..ad27da92e013 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 @@ public class CropView extends View { 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 7c3072df0875..6a2c60276186 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 @@ public class AmbientState implements Dumpable { /** 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 @@ public class AmbientState implements Dumpable { } /** + * @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 @@ public class AmbientState implements Dumpable { } /** + * @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 @@ public class AmbientState implements Dumpable { 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 07b0b830cf6c..f26f8405e299 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 @@ public class NotificationStackScrollLayout @Override public void setHeadsUpTop(float headsUpTop) { mAmbientState.setHeadsUpTop(headsUpTop); + requestChildrenUpdate(); } @Override @@ -1563,6 +1564,12 @@ public class NotificationStackScrollLayout } } + @Override + public void setQsExpandFraction(float expandFraction) { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; + mAmbientState.setQsExpansionFraction(expandFraction); + } + /** * Update the height of the panel. * @@ -2534,6 +2541,11 @@ public class NotificationStackScrollLayout 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 f35d6660edd9..55f05662f0c6 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.notification.stack.ui.viewbinder.Notificat 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 @@ public class NotificationStackScrollLayoutController implements Dumpable { mHeadsUpManager, statusBarService.get(), getHeadsUpCallback(), - new HeadsUpNotificationViewControllerEmptyImpl() + getHeadsUpNotificationViewController() ); } mNotificationRoundnessManager = notificationRoundnessManager; @@ -1851,6 +1852,32 @@ public class NotificationStackScrollLayoutController implements Dumpable { 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 aee1d3e08256..0c2b5aeb6feb 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 @@ public class StackScrollAlgorithm { 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 @@ public class StackScrollAlgorithm { 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 @@ public class StackScrollAlgorithm { /* 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 @@ public class StackScrollAlgorithm { /* baseZ = */ baseZ, /* viewState = */ childState ); + } else { + childState.setZTranslation(baseZ); } if (isTopEntry && row.isAboveShelf()) { clampHunToMaxTranslation( @@ -1081,7 +1097,7 @@ public class StackScrollAlgorithm { 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 6226fe7952f6..288924dd4000 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 @@ interface NotificationScrollView { /** 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 950b14d2fafc..40761e07e33c 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 @@ constructor( 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 ed69e6f1acef..6489264fc2a7 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 @@ constructor( .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 75861331d932..f16fcb5d4e82 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 @@ import kotlinx.coroutines.flow.map class ZenModeInteractor @Inject constructor( + private val context: Context, private val zenModeRepository: ZenModeRepository, private val notificationSettingsRepository: NotificationSettingsRepository, ) { @@ -78,6 +79,26 @@ constructor( 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 @@ constructor( * 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 02b5e491047a..be90bec03e52 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 @@ constructor( 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 c425e8224a4c..5fc19711a26c 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 @@ package com.android.systemui.clipboardoverlay; 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.CLIPBO 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.Insets; 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 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { 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 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { } @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 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS) public void test_viewCallbacks_onShareTapped() { initController(); mOverlayController.setClipData(mSampleClipData, ""); @@ -324,8 +326,8 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { } @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 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS) public void test_viewCallbacks_onDismissTapped() { initController(); @@ -350,7 +353,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { @Test public void test_multipleDismissals_dismissesOnce_sharedTransitionsOff() { - mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false); initController(); mCallbacks.onSwipeDismissInitiated(mAnimator); mCallbacks.onDismissButtonTapped(); @@ -362,6 +364,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { } @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 c2f035f1c241..19735e29834e 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 @@ class ModesTileTest : SysuiTestCase() { 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 a5fbfb539354..be9fcc2be3a3 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 @@ package com.android.systemui.screenshot.policy 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.junit.runner.RunWith 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 @@ class WorkProfilePolicyTest { @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 ad029d7e7ef2..7e790197ccd1 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 @@ class StackScrollAlgorithmTest : SysuiTestCase() { 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 aef08286262b..66be7e7a7a7e 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 @@ import com.android.systemui.statusbar.policy.data.repository.zenModeRepository 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 9c6bcdf8a67c..53885fc5799e 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 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { 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 35c6c783ab43..4bc6e70d545f 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 @@ public interface ServiceConfig { /** * 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 b203ead81121..e090317e07dd 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 @@ import android.provider.DeviceConfig; * 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 8abbe5666d58..22eefb3b73c2 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 @@ import java.util.List; /** * 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/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java index 8da58cfd6b23..d3e808fbd3d1 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 @@ class InputController { } /** 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 @@ class InputController { 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 e84250dff028..47203fbf2ef0 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -296,7 +296,9 @@ public class PackageWatchdog { 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 68d92218c01b..f1bdc05cce3c 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.ALLOW_NON_FULL; 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.PowerExemptionManager.getReasonCodeFromProcState; 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_ALL; 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_PROCESSES; 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; 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.database.ContentObserver; 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.provider.Settings; 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.IntArray; 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.FastPrintWriter; 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.SystemService; 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.graphics.fonts.FontManagerInternal; 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.Collections; 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub // 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 @@ public class ActivityManagerService extends IActivityManager.Stub final AppErrors mAppErrors; final PackageWatchdog mPackageWatchdog; + final CrashRecoveryHelper mCrashRecoveryHelper; @GuardedBy("mDeliveryGroupPolicyIgnoredActions") private final ArraySet<String> mDeliveryGroupPolicyIgnoredActions = new ArraySet(); @@ -860,12 +826,6 @@ public class ActivityManagerService extends IActivityManager.Stub }; /** - * 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub // 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub mPendingStartActivityUids = new PendingStartActivityUids(); mUseFifoUiScheduling = false; mBroadcastQueue = injector.getBroadcastQueue(this); + mBroadcastController = new BroadcastController(mContext, this, mBroadcastQueue); mComponentAliasResolver = new ComponentAliasResolver(this); } @@ -2579,9 +2452,11 @@ public class ActivityManagerService extends IActivityManager.Stub : 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 @@ public class ActivityManagerService extends IActivityManager.Stub void setBroadcastQueueForTest(BroadcastQueue broadcastQueue) { mBroadcastQueue = broadcastQueue; + mBroadcastController.setBroadcastQueueForTest(broadcastQueue); } BroadcastQueue getBroadcastQueue() { @@ -2680,18 +2556,6 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub } /** 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub mPackageManagerInt.sendPackageRestartedBroadcast(packageName, uid, flags); } - private void cleanupDisabledPackageComponentsLocked( + void cleanupDisabledPackageComponentsLocked( String packageName, int userId, String[] changedClasses) { Set<String> disabledClasses = null; @@ -4454,9 +4318,7 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub } 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub } 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 @@ public class ActivityManagerService extends IActivityManager.Stub // 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } - 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } - @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 @@ public class ActivityManagerService extends IActivityManager.Stub // 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 class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub } @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 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void enforceBroadcastOptionsPermissions(Bundle options, int callingUid) { - enforceBroadcastOptionPermissionsInternal(options, callingUid); + mBroadcastController.enforceBroadcastOptionPermissionsInternal(options, callingUid); } /** @@ -19219,7 +17046,7 @@ public class ActivityManagerService extends IActivityManager.Stub @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 @@ public class ActivityManagerService extends IActivityManager.Stub @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 @@ public class ActivityManagerService extends IActivityManager.Stub } } - /** - * 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 000000000000..32026b28f18b --- /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 7d26004b15c5..54884460a653 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 @@ public class BrightnessSetting { } /** + * 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/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index ab7971355041..a887f6d241e6 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 e157b0560365..72a91d59c911 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 @@ public final class DisplayBrightnessController { } /** + * 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/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index ee0159d722b1..4665a72b0b06 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 @@ public class ComputerEngine implements Computer { 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/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 530c03f350da..9d91d3d44c4f 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 9be3f436b819..670a61dca5c8 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 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { * 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 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { } 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 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { 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 92813a802a63..08b1e370c977 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 @@ class Task extends TaskFragment { */ 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 @@ class Task extends TaskFragment { 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/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 9d40b16c37ae..e1e64ee10245 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 @@ package com.android.server.wm; 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 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub 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 2bae0a826417..d2aebdeed99a 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 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio 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 4568f2eac95c..eed0cf78baf4 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 6984f0dbab72..2c5a4538863e 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.Log; 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 @@ public final class WindowTracingDataSource extends DataSource<WindowTracingDataS 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 @@ public final class WindowTracingDataSource extends DataSource<WindowTracingDataS 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 d5013517cd88..1290fb7ef91a 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @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/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index 5840cb937e67..2166cb7639ef 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 @@ public final class DisplayPowerControllerTest { /* 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 e610a32cb549..809e13c65225 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_SERVICE; 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.ArgumentMatchers.nullable; 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.AppOpsManager; 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 androidx.test.platform.app.InstrumentationRegistry; 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; |