Merge "Introduce overlays in SceneTransitionLayout (1/2)" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index baf142a..c7df662 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9875,6 +9875,7 @@
field public static final int RESULT_DISCOVERY_TIMEOUT = 2; // 0x2
field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
field public static final int RESULT_OK = -1; // 0xffffffff
+ field @FlaggedApi("android.companion.association_failure_code") public static final int RESULT_SECURITY_ERROR = 4; // 0x4
field public static final int RESULT_USER_REJECTED = 1; // 0x1
}
@@ -9884,7 +9885,7 @@
method public void onAssociationPending(@NonNull android.content.IntentSender);
method @Deprecated public void onDeviceFound(@NonNull android.content.IntentSender);
method public abstract void onFailure(@Nullable CharSequence);
- method @FlaggedApi("android.companion.association_failure_code") public void onFailure(int);
+ method @FlaggedApi("android.companion.association_failure_code") public void onFailure(int, @Nullable CharSequence);
}
public abstract class CompanionDeviceService extends android.app.Service {
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 1529842..1cdf3b1 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -21,7 +21,6 @@
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER;
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH;
-import static java.util.Collections.unmodifiableMap;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
@@ -58,7 +57,6 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
-import android.util.ArrayMap;
import android.util.ExceptionUtils;
import android.util.Log;
import android.util.SparseArray;
@@ -78,7 +76,6 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
@@ -146,12 +143,19 @@
/**
* The result code to propagate back to the user activity, indicates the internal error
* in CompanionDeviceManager.
- * E.g. Missing necessary permissions or duplicate {@link AssociationRequest}s when create the
- * {@link AssociationInfo}.
*/
public static final int RESULT_INTERNAL_ERROR = 3;
/**
+ * The result code to propagate back to the user activity and
+ * {@link Callback#onFailure(int, CharSequence)}, indicates app is not allow to create the
+ * association due to the security issue.
+ * E.g. There are missing necessary permissions when creating association.
+ */
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_FAILURE_CODE)
+ public static final int RESULT_SECURITY_ERROR = 4;
+
+ /**
* Requesting applications will receive the String in {@link Callback#onFailure} if the
* association dialog is explicitly declined by the users. E.g. press the Don't allow
* button.
@@ -374,7 +378,6 @@
*/
public void onAssociationCreated(@NonNull AssociationInfo associationInfo) {}
- //TODO(b/331459560): Add deprecated and remove abstract after API cut for W.
/**
* Invoked if the association could not be created.
*
@@ -385,11 +388,15 @@
/**
* Invoked if the association could not be created.
*
- * @param resultCode indicate the particular reason why the association
- * could not be created.
+ * Please note that both {@link #onFailure(CharSequence error)} and this
+ * API will be called if the association could not be created.
+ *
+ * @param errorCode indicate the particular error code why the association
+ * could not be created.
+ * @param error error message.
*/
@FlaggedApi(Flags.FLAG_ASSOCIATION_FAILURE_CODE)
- public void onFailure(@ResultCode int resultCode) {}
+ public void onFailure(@ResultCode int errorCode, @Nullable CharSequence error) {}
}
private final ICompanionDeviceManager mService;
@@ -1825,12 +1832,12 @@
}
@Override
- public void onFailure(@ResultCode int resultCode) {
+ public void onFailure(@ResultCode int errorCode, @Nullable CharSequence error) {
if (Flags.associationFailureCode()) {
- execute(mCallback::onFailure, resultCode);
+ execute(mCallback::onFailure, errorCode, error);
}
- execute(mCallback::onFailure, RESULT_CODE_TO_REASON.get(resultCode));
+ execute(mCallback::onFailure, error);
}
private <T> void execute(Consumer<T> callback, T arg) {
@@ -1840,6 +1847,12 @@
mHandler.post(() -> callback.accept(arg));
}
}
+
+ private <T, U> void execute(BiConsumer<T, U> callback, T arg1, U arg2) {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> callback.accept(arg1, arg2));
+ }
+ }
}
private static class OnAssociationsChangedListenerProxy
@@ -2014,15 +2027,4 @@
}
}
}
-
- private static final Map<Integer, String> RESULT_CODE_TO_REASON;
- static {
- final Map<Integer, String> map = new ArrayMap<>();
- map.put(RESULT_CANCELED, REASON_CANCELED);
- map.put(RESULT_USER_REJECTED, REASON_USER_REJECTED);
- map.put(RESULT_DISCOVERY_TIMEOUT, REASON_DISCOVERY_TIMEOUT);
- map.put(RESULT_INTERNAL_ERROR, REASON_INTERNAL_ERROR);
-
- RESULT_CODE_TO_REASON = unmodifiableMap(map);
- }
}
diff --git a/core/java/android/companion/IAssociationRequestCallback.aidl b/core/java/android/companion/IAssociationRequestCallback.aidl
index b1be30a..a6f86a5 100644
--- a/core/java/android/companion/IAssociationRequestCallback.aidl
+++ b/core/java/android/companion/IAssociationRequestCallback.aidl
@@ -25,5 +25,5 @@
oneway void onAssociationCreated(in AssociationInfo associationInfo);
- oneway void onFailure(in int resultCode);
+ oneway void onFailure(in int errorCode, in CharSequence error);
}
\ No newline at end of file
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index cd3ce87..5779a44 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -2501,33 +2501,19 @@
return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true, indent);
}
- private void addIndentOrComma(StringBuilder sb, String indent) {
- if (indent != null) {
- sb.append("\n ");
- sb.append(indent);
- } else {
- sb.append(", ");
- }
+ /** @hide */
+ public String toSimpleString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(mId);
+ addReadableFlags(sb);
+ return sb.toString();
}
- private String toStringInner(boolean secure, boolean includeInternalData, String indent) {
- final StringBuilder sb = new StringBuilder();
-
- if (indent != null) {
- sb.append(indent);
- }
-
- sb.append("ShortcutInfo {");
-
- sb.append("id=");
- sb.append(secure ? "***" : mId);
-
- sb.append(", flags=0x");
- sb.append(Integer.toHexString(mFlags));
+ private void addReadableFlags(StringBuilder sb) {
sb.append(" [");
if ((mFlags & FLAG_SHADOW) != 0) {
- // Note the shadow flag isn't actually used anywhere and it's just for dumpsys, so
- // we don't have an isXxx for this.
+ // Note the shadow flag isn't actually used anywhere and it's
+ // just for dumpsys, so we don't have an isXxx for this.
sb.append("Sdw");
}
if (!isEnabled()) {
@@ -2576,7 +2562,32 @@
sb.append("Hid-L");
}
sb.append("]");
+ }
+ private void addIndentOrComma(StringBuilder sb, String indent) {
+ if (indent != null) {
+ sb.append("\n ");
+ sb.append(indent);
+ } else {
+ sb.append(", ");
+ }
+ }
+
+ private String toStringInner(boolean secure, boolean includeInternalData, String indent) {
+ final StringBuilder sb = new StringBuilder();
+
+ if (indent != null) {
+ sb.append(indent);
+ }
+
+ sb.append("ShortcutInfo {");
+
+ sb.append("id=");
+ sb.append(secure ? "***" : mId);
+
+ sb.append(", flags=0x");
+ sb.append(Integer.toHexString(mFlags));
+ addReadableFlags(sb);
addIndentOrComma(sb, indent);
sb.append("packageName=");
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 7665fe8..04a810a 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -461,6 +461,12 @@
@SuppressLint("NonUserGetterCalled")
public boolean registerClient(Context ctx, IBinder token, int extension,
String cameraId, Map<String, CameraMetadataNative> characteristicsMapNative) {
+ if (!SystemProperties.getBoolean("ro.camerax.extensions.enabled",
+ /*default*/ false)) {
+ Log.v(TAG, "Disabled camera extension property!");
+ return false;
+ }
+
boolean ret = registerClientHelper(ctx, token, extension, false /*useFallback*/);
if (Flags.concertMode()) {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 1922327..eb35817 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -83,7 +83,6 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.StrictMode;
-import android.os.Trace;
import android.os.UserHandle;
import android.system.Os;
import android.text.TextUtils;
@@ -6247,18 +6246,6 @@
private View inflateView(Context context, RemoteViews rv, @Nullable ViewGroup parent,
@StyleRes int applyThemeResId, @Nullable ColorResources colorResources) {
- try {
- Trace.beginSection(rv.hasDrawInstructions()
- ? "RemoteViews#inflateViewWithDrawInstructions"
- : "RemoteViews#inflateView");
- return inflateViewInternal(context, rv, parent, applyThemeResId, colorResources);
- } finally {
- Trace.endSection();
- }
- }
-
- private View inflateViewInternal(Context context, RemoteViews rv, @Nullable ViewGroup parent,
- @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) {
// RemoteViews may be built by an application installed in another
// user. So build a context that loads resources from that user but
// still returns the current users userId so settings like data / time formats
@@ -6397,7 +6384,7 @@
private View mResult;
private ViewTree mTree;
- private List<Action> mActions;
+ private Action[] mActions;
private Exception mError;
private AsyncApplyTask(
@@ -6424,20 +6411,11 @@
if (mRV.mActions != null) {
int count = mRV.mActions.size();
- mActions = new ArrayList<>(count);
- try {
- Trace.beginSection(hasDrawInstructions()
- ? "RemoteViews#initActionAsyncWithDrawInstructions"
- : "RemoteViews#initActionAsync");
- for (Action action : mRV.mActions) {
- if (isCancelled()) {
- break;
- }
- // TODO: check if isCancelled in nested views.
- mActions.add(action.initActionAsync(mTree, mParent, mApplyParams));
- }
- } finally {
- Trace.endSection();
+ mActions = new Action[count];
+ for (int i = 0; i < count && !isCancelled(); i++) {
+ // TODO: check if isCancelled in nested views.
+ mActions[i] = mRV.mActions.get(i)
+ .initActionAsync(mTree, mParent, mApplyParams);
}
} else {
mActions = null;
@@ -6459,7 +6437,14 @@
try {
if (mActions != null) {
- mRV.performApply(viewTree.mRoot, mParent, mApplyParams, mActions);
+
+ ActionApplyParams applyParams = mApplyParams.clone();
+ if (applyParams.handler == null) {
+ applyParams.handler = DEFAULT_INTERACTION_HANDLER;
+ }
+ for (Action a : mActions) {
+ a.apply(viewTree.mRoot, mParent, applyParams);
+ }
}
// If the parent of the view is has is a root, resolve the recycling.
if (mTopLevel && mResult instanceof ViewGroup) {
@@ -6635,11 +6620,6 @@
}
private void performApply(View v, ViewGroup parent, ActionApplyParams params) {
- performApply(v, parent, params, mActions);
- }
-
- private void performApply(
- View v, ViewGroup parent, ActionApplyParams params, List<Action> actions) {
params = params.clone();
if (params.handler == null) {
params.handler = DEFAULT_INTERACTION_HANDLER;
@@ -6650,15 +6630,8 @@
}
if (mActions != null) {
final int count = mActions.size();
- try {
- Trace.beginSection(hasDrawInstructions()
- ? "RemoteViews#applyActionsWithDrawInstructions"
- : "RemoteViews#applyActions");
- for (int i = 0; i < count; i++) {
- mActions.get(i).apply(v, parent, params);
- }
- } finally {
- Trace.endSection();
+ for (int i = 0; i < count; i++) {
+ mActions.get(i).apply(v, parent, params);
}
}
}
diff --git a/core/java/com/android/internal/accessibility/TEST_MAPPING b/core/java/com/android/internal/accessibility/TEST_MAPPING
index 1c67399..b2b3041 100644
--- a/core/java/com/android/internal/accessibility/TEST_MAPPING
+++ b/core/java/com/android/internal/accessibility/TEST_MAPPING
@@ -2,6 +2,9 @@
"imports": [
{
"path": "frameworks/base/services/accessibility/TEST_MAPPING"
+ },
+ {
+ "path": "frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/TEST_MAPPING"
}
]
}
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 1d9d366..1204ef3 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -698,7 +698,7 @@
case CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE:
return "DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE";
case CUJ_DESKTOP_MODE_SNAP_RESIZE:
- return "CUJ_DESKTOP_MODE_SNAP_RESIZE";
+ return "DESKTOP_MODE_SNAP_RESIZE";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
index 2feb3d5..8771cde 100644
--- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
@@ -29,6 +29,7 @@
import static com.android.internal.protolog.ProtoLogMessage.SINT64_PARAMS;
import static com.android.internal.protolog.ProtoLogMessage.STR_PARAMS;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.ShellCommand;
import android.os.SystemClock;
@@ -49,6 +50,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
@@ -419,6 +421,12 @@
return group.isLogToLogcat() || (group.isLogToProto() && isProtoEnabled());
}
+ @Override
+ @NonNull
+ public List<IProtoLogGroup> getRegisteredGroups() {
+ return mLogGroups.values().stream().toList();
+ }
+
public void registerGroups(IProtoLogGroup... protoLogGroups) {
for (IProtoLogGroup group : protoLogGroups) {
mLogGroups.put(group.name(), group);
diff --git a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
index e8d5195..b82c660 100644
--- a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
@@ -18,6 +18,7 @@
import static com.android.internal.protolog.ProtoLog.REQUIRE_PROTOLOGTOOL;
+import android.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
@@ -26,6 +27,9 @@
import com.android.internal.protolog.common.IProtoLogGroup;
import com.android.internal.protolog.common.LogLevel;
+import java.util.Collections;
+import java.util.List;
+
/**
* Class only create and used to server temporarily for when there is source code pre-processing by
* the ProtoLog tool, when the tracing to Perfetto flag is off, and the static REQUIRE_PROTOLOGTOOL
@@ -79,4 +83,10 @@
public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
return true;
}
+
+ @Override
+ @NonNull
+ public List<IProtoLogGroup> getRegisteredGroups() {
+ return Collections.emptyList();
+ }
}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 49ed55d..5517967 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -306,6 +306,12 @@
|| group.isLogToLogcat();
}
+ @Override
+ @NonNull
+ public List<IProtoLogGroup> getRegisteredGroups() {
+ return mLogGroups.values().stream().toList();
+ }
+
private void registerGroupsLocally(@NonNull IProtoLogGroup[] protoLogGroups) {
final var groupsLoggingToLogcat = new ArrayList<String>();
for (IProtoLogGroup protoLogGroup : protoLogGroups) {
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index f9b9894..660d3c9 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -20,6 +20,9 @@
import com.android.internal.protolog.common.IProtoLogGroup;
import com.android.internal.protolog.common.LogLevel;
+import java.util.ArrayList;
+import java.util.Arrays;
+
/**
* ProtoLog API - exposes static logging methods. Usage of this API is similar
* to {@code android.utils.Log} class. Instead of plain text log messages each call consists of
@@ -49,6 +52,8 @@
private static IProtoLog sProtoLogInstance;
+ private static final Object sInitLock = new Object();
+
/**
* Initialize ProtoLog in this process.
* <p>
@@ -59,7 +64,17 @@
*/
public static void init(IProtoLogGroup... groups) {
if (android.tracing.Flags.perfettoProtologTracing()) {
- sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+ synchronized (sInitLock) {
+ if (sProtoLogInstance != null) {
+ // The ProtoLog instance has already been initialized in this process
+ final var alreadyRegisteredGroups = sProtoLogInstance.getRegisteredGroups();
+ final var allGroups = new ArrayList<>(alreadyRegisteredGroups);
+ allGroups.addAll(Arrays.stream(groups).toList());
+ groups = allGroups.toArray(new IProtoLogGroup[0]);
+ }
+
+ sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+ }
} else {
// The first call to ProtoLog is likely to flip REQUIRE_PROTOLOGTOOL, which is when this
// static block will be executed before REQUIRE_PROTOLOGTOOL is actually set.
diff --git a/core/java/com/android/internal/protolog/common/IProtoLog.java b/core/java/com/android/internal/protolog/common/IProtoLog.java
index d5c2ac1..f06f08a 100644
--- a/core/java/com/android/internal/protolog/common/IProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/IProtoLog.java
@@ -16,6 +16,8 @@
package com.android.internal.protolog.common;
+import java.util.List;
+
/**
* Interface for ProtoLog implementations.
*/
@@ -68,4 +70,9 @@
* @return If we need to log this group and level to either ProtoLog or Logcat.
*/
boolean isEnabled(IProtoLogGroup group, LogLevel level);
+
+ /**
+ * @return an immutable list of the registered ProtoLog groups in this ProtoLog instance.
+ */
+ List<IProtoLogGroup> getRegisteredGroups();
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f795406..2dd560c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5605,12 +5605,13 @@
<!-- This permission is required among systems services to always keep the
binding with TvInputManagerService.
<p>This should only be used by the OEM TvInputService.
+ @FlaggedApi("android.media.tv.flags.tif_unbind_inactive_tis")
<p>Protection level: signature|privileged|vendorPrivileged
@hide
-->
<permission android:name="android.permission.ALWAYS_BOUND_TV_INPUT"
android:protectionLevel="signature|privileged|vendorPrivileged"
- android:featureFlag="android.media.tv.flags.tis_always_bound_permission"/>
+ android:featureFlag="android.media.tv.flags.tif_unbind_inactive_tis"/>
<!-- Must be required by a {@link android.media.tv.interactive.TvInteractiveAppService}
to ensure that only the system can bind to it.
diff --git a/core/res/res/color/input_method_switch_on_item.xml b/core/res/res/color/input_method_switch_on_item.xml
new file mode 100644
index 0000000..49fe081
--- /dev/null
+++ b/core/res/res/color/input_method_switch_on_item.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_activated="true" android:color="?attr/materialColorOnSecondaryContainer" />
+ <item android:color="?attr/materialColorOnSurface" />
+</selector>
diff --git a/core/res/res/layout/input_method_switch_item_new.xml b/core/res/res/layout/input_method_switch_item_new.xml
index 09ed650..10d938c 100644
--- a/core/res/res/layout/input_method_switch_item_new.xml
+++ b/core/res/res/layout/input_method_switch_item_new.xml
@@ -70,6 +70,7 @@
android:ellipsize="marquee"
android:singleLine="true"
android:fontFamily="google-sans-text"
+ android:textColor="@color/input_method_switch_on_item"
android:textAppearance="?attr/textAppearanceListItem"/>
</LinearLayout>
@@ -81,7 +82,7 @@
android:gravity="center_vertical"
android:layout_marginStart="12dp"
android:src="@drawable/ic_check_24dp"
- android:tint="?attr/materialColorOnSurface"
+ android:tint="@color/input_method_switch_on_item"
android:visibility="gone"
android:importantForAccessibility="no"/>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index de7477e..b6468ee 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3080,6 +3080,11 @@
<!-- Whether UI for multi user should be shown -->
<bool name="config_enableMultiUserUI">false</bool>
+ <!-- Whether to boot system with the headless system user, i.e. user 0. If set to true,
+ system will be booted with the headless system user, or user 0. It has no effect if device
+ is not in Headless System User Mode (HSUM). -->
+ <bool name="config_bootToHeadlessSystemUser">false</bool>
+
<!-- Whether multiple admins are allowed on the device. If set to true, new users can be created
with admin privileges and admin privileges can be granted/revoked from existing users. -->
<bool name="config_enableMultipleAdmins">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0d16e9c..9a52bd4 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -363,6 +363,7 @@
<java-symbol type="bool" name="config_canSwitchToHeadlessSystemUser"/>
<java-symbol type="bool" name="config_enableMultiUserUI"/>
<java-symbol type="bool" name="config_enableMultipleAdmins"/>
+ <java-symbol type="bool" name="config_bootToHeadlessSystemUser"/>
<java-symbol type="bool" name="config_omnipresentCommunalUser"/>
<java-symbol type="bool" name="config_enableNewAutoSelectNetworkUI"/>
<java-symbol type="bool" name="config_disableUsbPermissionDialogs"/>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
index 434885f..47d5274 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
@@ -22,10 +22,14 @@
import com.android.window.flags.Flags
/*
- * A shared class to check desktop mode flags state.
+ * An enum to check desktop mode flags state.
*
- * The class computes whether a Desktop Windowing flag should be enabled by using the aconfig flag
- * value and the developer option override state (if applicable).
+ * This enum provides a centralized way to control the behavior of flags related to desktop
+ * windowing features which are aiming for developer preview before their release. It allows
+ * developer option to override the default behavior of these flags.
+ *
+ * NOTE: Flags should only be added to this enum when they have received Product and UX
+ * alignment that the feature is ready for developer preview, otherwise just do a flag check.
*/
enum class DesktopModeFlags(
// Function called to obtain aconfig flag value.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 8ce7837..17869e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -19,8 +19,6 @@
import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
-import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.TaskInfo;
@@ -40,6 +38,7 @@
import com.android.wm.shell.compatui.api.CompatUIEvent;
import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonAppeared;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import java.util.function.Consumer;
@@ -83,7 +82,7 @@
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mCallback = callback;
mHasSizeCompat = taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat();
- if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
+ if (DesktopModeStatus.canEnterDesktopMode(context)
&& DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(context)) {
// Don't show the SCM button for freeform tasks
mHasSizeCompat &= !taskInfo.isFreeform();
@@ -139,7 +138,7 @@
boolean canShow) {
final boolean prevHasSizeCompat = mHasSizeCompat;
mHasSizeCompat = taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat();
- if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
+ if (DesktopModeStatus.canEnterDesktopMode(mContext)
&& DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)) {
// Don't show the SCM button for freeform tasks
mHasSizeCompat &= !taskInfo.isFreeform();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 447477a..ffd534b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -323,7 +323,7 @@
logW("moveBackgroundTaskToDesktop taskId=%d not found", taskId)
return false
}
- logV("moveBackgroundTaskToDesktop with taskId=%d, displayId=%d", taskId)
+ logV("moveBackgroundTaskToDesktop with taskId=%d", taskId)
// TODO(342378842): Instead of using default display, support multiple displays
val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
DEFAULT_DISPLAY, wct, taskId)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 7ba6ec4..b102e40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -41,6 +41,7 @@
import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_CLEANUP_PIP_EXIT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
@@ -122,6 +123,8 @@
@Nullable
private IBinder mMoveToBackTransition;
private IBinder mRequestedEnterTransition;
+ private IBinder mCleanupTransition;
+
private WindowContainerToken mRequestedEnterTask;
/** The Task window that is currently in PIP windowing mode. */
@Nullable
@@ -232,10 +235,12 @@
// Exiting PIP.
final int type = info.getType();
- if (transition.equals(mExitTransition) || transition.equals(mMoveToBackTransition)) {
+ if (transition.equals(mExitTransition) || transition.equals(mMoveToBackTransition)
+ || transition.equals(mCleanupTransition)) {
mExitDestinationBounds.setEmpty();
mExitTransition = null;
mMoveToBackTransition = null;
+ mCleanupTransition = null;
mHasFadeOut = false;
if (mFinishCallback != null) {
callFinishCallback(null /* wct */);
@@ -269,6 +274,9 @@
removePipImmediately(info, startTransaction, finishTransaction, finishCallback,
pipTaskInfo);
break;
+ case TRANSIT_CLEANUP_PIP_EXIT:
+ cleanupPipExitTransition(startTransaction, finishCallback);
+ break;
default:
throw new IllegalStateException("mExitTransition with unexpected transit type="
+ transitTypeToString(type));
@@ -768,7 +776,19 @@
mPipAnimationController.resetAnimatorState();
finishTransaction.remove(pipLeash);
}
- finishCallback.onTransitionFinished(wct);
+
+ if (mFixedRotationState == FIXED_ROTATION_TRANSITION) {
+ // TODO(b/358226697): start a new transition with the WCT instead of applying it in
+ // the {@link finishCallback}, to ensure shell creates a transition for it.
+ finishCallback.onTransitionFinished(wct);
+ } else {
+ // Apply wct in separate transition so that it can be correctly handled by the
+ // {@link FreeformTaskTransitionObserver} when desktop windowing (which does not
+ // utilize fixed rotation transitions for exiting pip) is enabled (See b/288910069).
+ mCleanupTransition = mTransitions.startTransition(
+ TRANSIT_CLEANUP_PIP_EXIT, wct, this);
+ finishCallback.onTransitionFinished(null);
+ }
};
mFinishTransaction = finishTransaction;
@@ -914,6 +934,16 @@
finishCallback.onTransitionFinished(null);
}
+ /**
+ * For {@link Transitions#TRANSIT_CLEANUP_PIP_EXIT} which applies final config changes needed
+ * after the exit from pip transition animation finishes.
+ */
+ private void cleanupPipExitTransition(@NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ }
+
/** Whether we should handle the given {@link TransitionInfo} animation as entering PIP. */
private boolean isEnteringPip(@NonNull TransitionInfo info) {
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index f0d3668..7dc336b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -193,6 +193,9 @@
/** Remote Transition that split accepts but ultimately needs to be animated by the remote. */
public static final int TRANSIT_SPLIT_PASSTHROUGH = TRANSIT_FIRST_CUSTOM + 18;
+ /** Transition to set windowing mode after exit pip transition is finished animating. */
+ public static final int TRANSIT_CLEANUP_PIP_EXIT = WindowManager.TRANSIT_FIRST_CUSTOM + 19;
+
/** Transition type for desktop mode transitions. */
public static final int TRANSIT_DESKTOP_MODE_TYPES =
WindowManager.TRANSIT_FIRST_CUSTOM + 100;
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index 8584b59..3fb67cd 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -24,14 +24,19 @@
import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
import android.tools.flicker.assertors.assertions.AppWindowHasSizeOfAtLeast
import android.tools.flicker.assertors.assertions.AppWindowIsInvisibleAtEnd
+import android.tools.flicker.assertors.assertions.AppWindowIsVisibleAlways
+import android.tools.flicker.assertors.assertions.AppWindowMaintainsAspectRatioAlways
import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd
import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart
+import android.tools.flicker.assertors.assertions.AppWindowRemainInsideDisplayBounds
+import android.tools.flicker.assertors.assertions.AppWindowReturnsToStartBoundsAndPosition
import android.tools.flicker.assertors.assertions.LauncherWindowReplacesAppAsTopWindow
import android.tools.flicker.config.AssertionTemplates
import android.tools.flicker.config.FlickerConfigEntry
import android.tools.flicker.config.ScenarioId
-import android.tools.flicker.config.desktopmode.Components
+import android.tools.flicker.config.desktopmode.Components.DESKTOP_MODE_APP
import android.tools.flicker.config.desktopmode.Components.DESKTOP_WALLPAPER
+import android.tools.flicker.config.desktopmode.Components.NON_RESIZABLE_APP
import android.tools.flicker.extractors.ITransitionMatcher
import android.tools.flicker.extractors.ShellTransitionScenarioExtractor
import android.tools.flicker.extractors.TaggedCujTransitionMatcher
@@ -62,13 +67,11 @@
assertions =
AssertionTemplates.COMMON_ASSERTIONS +
listOf(
- AppLayerIsVisibleAlways(Components.DESKTOP_MODE_APP),
- AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
- AppWindowHasDesktopModeInitialBoundsAtTheEnd(
- Components.DESKTOP_MODE_APP
- ),
- AppWindowBecomesVisible(DESKTOP_WALLPAPER)
- )
+ AppLayerIsVisibleAlways(DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(DESKTOP_MODE_APP),
+ AppWindowHasDesktopModeInitialBoundsAtTheEnd(DESKTOP_MODE_APP),
+ AppWindowBecomesVisible(DESKTOP_WALLPAPER)
+ )
.associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
@@ -97,11 +100,10 @@
assertions =
AssertionTemplates.COMMON_ASSERTIONS +
listOf(
- AppWindowOnTopAtStart(Components.DESKTOP_MODE_APP),
- AppLayerIsVisibleAtStart(Components.DESKTOP_MODE_APP),
- AppLayerIsInvisibleAtEnd(Components.DESKTOP_MODE_APP),
- )
- .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ AppWindowOnTopAtStart(DESKTOP_MODE_APP),
+ AppLayerIsVisibleAtStart(DESKTOP_MODE_APP),
+ AppLayerIsInvisibleAtEnd(DESKTOP_MODE_APP)
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
val CLOSE_LAST_APP =
@@ -125,10 +127,10 @@
assertions =
AssertionTemplates.COMMON_ASSERTIONS +
listOf(
- AppWindowIsInvisibleAtEnd(Components.DESKTOP_MODE_APP),
- LauncherWindowReplacesAppAsTopWindow(Components.DESKTOP_MODE_APP),
- AppWindowIsInvisibleAtEnd(DESKTOP_WALLPAPER)
- )
+ AppWindowIsInvisibleAtEnd(DESKTOP_MODE_APP),
+ LauncherWindowReplacesAppAsTopWindow(DESKTOP_MODE_APP),
+ AppWindowIsInvisibleAtEnd(DESKTOP_WALLPAPER)
+ )
.associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
@@ -156,9 +158,28 @@
)
.build(),
assertions =
- AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
- listOf(AppWindowHasSizeOfAtLeast(Components.DESKTOP_MODE_APP, 770, 700))
+ AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
+ listOf(AppWindowHasSizeOfAtLeast(DESKTOP_MODE_APP, 770, 700))
.associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
+
+ val SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE"),
+ extractor =
+ TaggedScenarioExtractorBuilder()
+ .setTargetTag(CujType.CUJ_DESKTOP_MODE_SNAP_RESIZE)
+ .setTransitionMatcher(
+ TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+ )
+ .build(),
+ assertions = listOf(
+ AppWindowIsVisibleAlways(NON_RESIZABLE_APP),
+ AppWindowOnTopAtEnd(NON_RESIZABLE_APP),
+ AppWindowRemainInsideDisplayBounds(NON_RESIZABLE_APP),
+ AppWindowMaintainsAspectRatioAlways(NON_RESIZABLE_APP),
+ AppWindowReturnsToStartBoundsAndPosition(NON_RESIZABLE_APP)
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowLeftWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowLeftWithDrag.kt
new file mode 100644
index 0000000..582658f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowLeftWithDrag.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE
+import com.android.wm.shell.scenarios.SnapResizeAppWindowWithDrag
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Snap resize non-resizable app window by dragging it to the left edge of the screen.
+ *
+ * Assert that the app window keeps the same size and returns to its original pre-drag position.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SnapResizeNonResizableAppWindowLeftWithDrag :
+ SnapResizeAppWindowWithDrag(toLeft = true, isResizable = false) {
+ @ExpectedScenarios(["SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE"])
+ @Test
+ override fun snapResizeAppWindowWithDrag() = super.snapResizeAppWindowWithDrag()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ .use(SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE)
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowRightWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowRightWithDrag.kt
new file mode 100644
index 0000000..7205ec4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowRightWithDrag.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE
+import com.android.wm.shell.scenarios.SnapResizeAppWindowWithDrag
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Snap resize non-resizable app window by dragging it to the right edge of the screen.
+ *
+ * Assert that the app window keeps the same size and returns to its original pre-drag position.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SnapResizeNonResizableAppWindowRightWithDrag :
+ SnapResizeAppWindowWithDrag(toLeft = false, isResizable = false) {
+ @ExpectedScenarios(["SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE"])
+ @Test
+ override fun snapResizeAppWindowWithDrag() = super.snapResizeAppWindowWithDrag()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ .use(SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE)
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
index e3660fe..b812c59 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
@@ -25,6 +25,7 @@
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.window.flags.Flags
import com.android.wm.shell.Utils
@@ -38,15 +39,20 @@
@RunWith(BlockJUnit4ClassRunner::class)
@Postsubmit
open class MaximizeAppWindow
-{
+@JvmOverloads
+constructor(rotation: Rotation = Rotation.ROTATION_0, isResizable: Boolean = true) {
+
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val tapl = LauncherInstrumentation()
private val wmHelper = WindowManagerStateHelper(instrumentation)
private val device = UiDevice.getInstance(instrumentation)
- private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val testApp = if (isResizable) {
+ DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ } else {
+ DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+ }
- @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL,
- Rotation.ROTATION_0)
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
@Before
fun setup() {
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
index 06775e1..685a3ba 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.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 @@
@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 871602f..8a4aa63 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.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 @@
@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/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index 1fe111e..e49eb36 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -376,7 +376,8 @@
callOnTransitionReady(transitionInfo)
- verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON, DEFAULT_TASK_UPDATE)
+ verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
}
@Test
@@ -398,7 +399,8 @@
.build()
callOnTransitionReady(transitionInfo)
- verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG, DEFAULT_TASK_UPDATE)
+ verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
}
@Test
diff --git a/libs/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h
index 31c9db7..3a3bfb47 100644
--- a/libs/hwui/ColorFilter.h
+++ b/libs/hwui/ColorFilter.h
@@ -106,7 +106,7 @@
private:
sk_sp<SkColorFilter> createInstance() override {
- return SkColorFilters::Matrix(mMatrix.data());
+ return SkColorFilters::Matrix(mMatrix.data(), SkColorFilters::Clamp::kNo);
}
private:
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 8bb11ba..dfda25d 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -761,8 +761,8 @@
if (mExpectSurfaceStats) {
reportMetricsWithPresentTime();
{ // acquire lock
- std::lock_guard lock(mLast4FrameMetricsInfosMutex);
- FrameMetricsInfo& next = mLast4FrameMetricsInfos.next();
+ std::lock_guard lock(mLastFrameMetricsInfosMutex);
+ FrameMetricsInfo& next = mLastFrameMetricsInfos.next();
next.frameInfo = mCurrentFrameInfo;
next.frameNumber = frameCompleteNr;
next.surfaceId = mSurfaceControlGenerationId;
@@ -816,12 +816,12 @@
int32_t surfaceControlId;
{ // acquire lock
- std::scoped_lock lock(mLast4FrameMetricsInfosMutex);
- if (mLast4FrameMetricsInfos.size() != mLast4FrameMetricsInfos.capacity()) {
+ std::scoped_lock lock(mLastFrameMetricsInfosMutex);
+ if (mLastFrameMetricsInfos.size() != mLastFrameMetricsInfos.capacity()) {
// Not enough frames yet
return;
}
- auto frameMetricsInfo = mLast4FrameMetricsInfos.front();
+ auto frameMetricsInfo = mLastFrameMetricsInfos.front();
forthBehind = frameMetricsInfo.frameInfo;
frameNumber = frameMetricsInfo.frameNumber;
surfaceControlId = frameMetricsInfo.surfaceId;
@@ -869,12 +869,12 @@
}
}
-FrameInfo* CanvasContext::getFrameInfoFromLast4(uint64_t frameNumber, uint32_t surfaceControlId) {
- std::scoped_lock lock(mLast4FrameMetricsInfosMutex);
- for (size_t i = 0; i < mLast4FrameMetricsInfos.size(); i++) {
- if (mLast4FrameMetricsInfos[i].frameNumber == frameNumber &&
- mLast4FrameMetricsInfos[i].surfaceId == surfaceControlId) {
- return mLast4FrameMetricsInfos[i].frameInfo;
+FrameInfo* CanvasContext::getFrameInfoFromLastFew(uint64_t frameNumber, uint32_t surfaceControlId) {
+ std::scoped_lock lock(mLastFrameMetricsInfosMutex);
+ for (size_t i = 0; i < mLastFrameMetricsInfos.size(); i++) {
+ if (mLastFrameMetricsInfos[i].frameNumber == frameNumber &&
+ mLastFrameMetricsInfos[i].surfaceId == surfaceControlId) {
+ return mLastFrameMetricsInfos[i].frameInfo;
}
}
@@ -894,7 +894,7 @@
}
uint64_t frameNumber = functions.getFrameNumberFunc(stats);
- FrameInfo* frameInfo = instance->getFrameInfoFromLast4(frameNumber, surfaceControlId);
+ FrameInfo* frameInfo = instance->getFrameInfoFromLastFew(frameNumber, surfaceControlId);
if (frameInfo != nullptr) {
std::scoped_lock lock(instance->mFrameInfoMutex);
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index e2e3fa3..cb37538 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -260,7 +260,7 @@
void finishFrame(FrameInfo* frameInfo);
/**
- * Invoke 'reportFrameMetrics' on the last frame stored in 'mLast4FrameInfos'.
+ * Invoke 'reportFrameMetrics' on the last frame stored in 'mLastFrameInfos'.
* Populate the 'presentTime' field before calling.
*/
void reportMetricsWithPresentTime();
@@ -271,7 +271,7 @@
int32_t surfaceId;
};
- FrameInfo* getFrameInfoFromLast4(uint64_t frameNumber, uint32_t surfaceControlId);
+ FrameInfo* getFrameInfoFromLastFew(uint64_t frameNumber, uint32_t surfaceControlId);
Frame getFrame();
@@ -336,9 +336,9 @@
// List of data of frames that are awaiting GPU completion reporting. Used to compute frame
// metrics and determine whether or not to report the metrics.
- RingBuffer<FrameMetricsInfo, 4> mLast4FrameMetricsInfos
- GUARDED_BY(mLast4FrameMetricsInfosMutex);
- std::mutex mLast4FrameMetricsInfosMutex;
+ RingBuffer<FrameMetricsInfo, 6> mLastFrameMetricsInfos
+ GUARDED_BY(mLastFrameMetricsInfosMutex);
+ std::mutex mLastFrameMetricsInfosMutex;
std::string mName;
JankTracker mJankTracker;
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 0829a90e..93bca21 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -26,11 +26,11 @@
}
flag {
- name: "tis_always_bound_permission"
+ name: "tif_unbind_inactive_tis"
is_exported: true
namespace: "media_tv"
- description: "Introduce ALWAYS_BOUND_TV_INPUT for TIS."
- bug: "332201346"
+ description: "Unbind hardware TIS when not needed"
+ bug: "279189366"
}
flag {
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index 6117330..7974a37 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -19,6 +19,7 @@
import static android.companion.CompanionDeviceManager.RESULT_CANCELED;
import static android.companion.CompanionDeviceManager.RESULT_DISCOVERY_TIMEOUT;
import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
+import static android.companion.CompanionDeviceManager.RESULT_SECURITY_ERROR;
import static android.companion.CompanionDeviceManager.RESULT_USER_REJECTED;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -33,6 +34,7 @@
import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_TITLES;
import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_PROFILES;
import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_SELF_MANAGED_PROFILES;
+import static com.android.companiondevicemanager.Utils.RESULT_CODE_TO_REASON;
import static com.android.companiondevicemanager.Utils.getApplicationLabel;
import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
import static com.android.companiondevicemanager.Utils.getIcon;
@@ -51,6 +53,7 @@
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
+import android.companion.Flags;
import android.companion.IAssociationRequestCallback;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -231,7 +234,7 @@
boolean forCancelDialog = intent.getBooleanExtra(EXTRA_FORCE_CANCEL_CONFIRMATION, false);
if (forCancelDialog) {
Slog.i(TAG, "Cancelling the user confirmation");
- cancel(RESULT_CANCELED);
+ cancel(RESULT_CANCELED, null);
return;
}
@@ -243,10 +246,15 @@
if (appCallback == null) {
return;
}
- Slog.e(TAG, "More than one AssociationRequests are processing.");
try {
- appCallback.onFailure(RESULT_INTERNAL_ERROR);
+ if (Flags.associationFailureCode()) {
+ appCallback.onFailure(
+ RESULT_SECURITY_ERROR, "More than one AssociationRequests are processing.");
+ } else {
+ appCallback.onFailure(
+ RESULT_INTERNAL_ERROR, "More than one AssociationRequests are processing.");
+ }
} catch (RemoteException ignore) {
}
}
@@ -257,7 +265,7 @@
// TODO: handle config changes without cancelling.
if (!isDone()) {
- cancel(RESULT_CANCELED); // will finish()
+ cancel(RESULT_CANCELED, null); // will finish()
}
}
@@ -331,7 +339,7 @@
&& CompanionDeviceDiscoveryService.getScanResult().getValue().isEmpty()) {
synchronized (LOCK) {
if (sDiscoveryStarted) {
- cancel(RESULT_DISCOVERY_TIMEOUT);
+ cancel(RESULT_DISCOVERY_TIMEOUT, null);
}
}
}
@@ -371,7 +379,7 @@
mCdmServiceReceiver.send(RESULT_CODE_ASSOCIATION_APPROVED, data);
}
- private void cancel(int failureCode) {
+ private void cancel(int errorCode, @Nullable CharSequence error) {
if (isDone()) {
Slog.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
return;
@@ -385,13 +393,14 @@
// First send callback to the app directly...
try {
- Slog.i(TAG, "Sending onFailure to app due to failureCode=" + failureCode);
- mAppCallback.onFailure(failureCode);
+ CharSequence errorMessage = error != null
+ ? error : RESULT_CODE_TO_REASON.get(errorCode);
+ mAppCallback.onFailure(errorCode, errorMessage);
} catch (RemoteException ignore) {
}
// ... then set result and finish ("sending" onActivityResult()).
- setResultAndFinish(null, failureCode);
+ setResultAndFinish(null, errorCode);
}
private void setResultAndFinish(@Nullable AssociationInfo association, int resultCode) {
@@ -436,7 +445,7 @@
}
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
- cancel(RESULT_INTERNAL_ERROR);
+ cancel(RESULT_INTERNAL_ERROR, e.getMessage());
return;
}
@@ -625,7 +634,7 @@
// Disable the button, to prevent more clicks.
v.setEnabled(false);
- cancel(RESULT_USER_REJECTED);
+ cancel(RESULT_USER_REJECTED, null);
}
private void onShowHelperDialog(View view) {
@@ -755,8 +764,8 @@
};
@Override
- public void onShowHelperDialogFailed() {
- cancel(RESULT_INTERNAL_ERROR);
+ public void onShowHelperDialogFailed(CharSequence errorMessage) {
+ cancel(RESULT_INTERNAL_ERROR, errorMessage);
}
@Override
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
index ec92987..b2d78da 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
@@ -54,7 +54,7 @@
private Button mButton;
interface CompanionVendorHelperDialogListener {
- void onShowHelperDialogFailed();
+ void onShowHelperDialogFailed(CharSequence error);
void onHelperDialogDismissed();
}
@@ -110,7 +110,7 @@
appLabel = getApplicationLabel(getContext(), packageName, userId);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
- mListener.onShowHelperDialogFailed();
+ mListener.onShowHelperDialogFailed(e.getMessage());
return;
}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
index 8c14f80..2f97132 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
@@ -16,6 +16,15 @@
package com.android.companiondevicemanager;
+import static android.companion.CompanionDeviceManager.REASON_CANCELED;
+import static android.companion.CompanionDeviceManager.REASON_DISCOVERY_TIMEOUT;
+import static android.companion.CompanionDeviceManager.REASON_USER_REJECTED;
+import static android.companion.CompanionDeviceManager.RESULT_CANCELED;
+import static android.companion.CompanionDeviceManager.RESULT_DISCOVERY_TIMEOUT;
+import static android.companion.CompanionDeviceManager.RESULT_USER_REJECTED;
+
+import static java.util.Collections.unmodifiableMap;
+
import android.annotation.NonNull;
import android.annotation.StringRes;
import android.content.Context;
@@ -31,6 +40,9 @@
import android.os.ResultReceiver;
import android.text.Html;
import android.text.Spanned;
+import android.util.ArrayMap;
+
+import java.util.Map;
/**
* Utilities.
@@ -40,6 +52,17 @@
"android.companion.vendor_icon";
private static final String COMPANION_DEVICE_ACTIVITY_VENDOR_NAME =
"android.companion.vendor_name";
+ // This map solely the common error messages that occur during the Association
+ // creation process.
+ static final Map<Integer, String> RESULT_CODE_TO_REASON;
+ static {
+ final Map<Integer, String> map = new ArrayMap<>();
+ map.put(RESULT_CANCELED, REASON_CANCELED);
+ map.put(RESULT_USER_REJECTED, REASON_USER_REJECTED);
+ map.put(RESULT_DISCOVERY_TIMEOUT, REASON_DISCOVERY_TIMEOUT);
+
+ RESULT_CODE_TO_REASON = unmodifiableMap(map);
+ }
/**
* Convert an instance of a "locally-defined" ResultReceiver to an instance of
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
index e58de64..5bd26e113 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
@@ -48,45 +48,14 @@
/* Used as credential suggestion or user action chip. */
@Composable
fun CredentialsScreenChip(
- label: String,
+ primaryText: @Composable () -> Unit,
+ secondaryText: (@Composable () -> Unit)? = null,
onClick: () -> Unit,
- secondaryLabel: String? = null,
icon: Drawable? = null,
isAuthenticationEntryLocked: Boolean? = null,
- textAlign: TextAlign = TextAlign.Center,
modifier: Modifier = Modifier,
colors: ChipColors = ChipDefaults.secondaryChipColors()
) {
- return CredentialsScreenChip(
- onClick,
- text = {
- WearButtonText(
- text = label,
- textAlign = textAlign,
- maxLines = 2
- )
- },
- secondaryLabel,
- icon,
- isAuthenticationEntryLocked,
- modifier,
- colors
- )
-}
-
-
-
-/* Used as credential suggestion or user action chip. */
-@Composable
-fun CredentialsScreenChip(
- onClick: () -> Unit,
- text: @Composable () -> Unit,
- secondaryLabel: String? = null,
- icon: Drawable? = null,
- isAuthenticationEntryLocked: Boolean? = null,
- modifier: Modifier = Modifier,
- colors: ChipColors = ChipDefaults.primaryChipColors(),
- ) {
val labelParam: (@Composable RowScope.() -> Unit) =
{
var horizontalArrangement = Arrangement.Start
@@ -94,19 +63,15 @@
horizontalArrangement = Arrangement.Center
}
Row(horizontalArrangement = horizontalArrangement, modifier = modifier.fillMaxWidth()) {
- text()
+ primaryText()
}
}
val secondaryLabelParam: (@Composable RowScope.() -> Unit)? =
- secondaryLabel?.let {
+ secondaryText?.let {
{
Row {
- WearSecondaryLabel(
- text = secondaryLabel,
- color = WearMaterialTheme.colors.onSurfaceVariant
- )
-
+ secondaryText()
if (isAuthenticationEntryLocked != null) {
if (isAuthenticationEntryLocked) {
Icon(
@@ -156,9 +121,19 @@
@Composable
fun CredentialsScreenChipPreview() {
CredentialsScreenChip(
- label = "Elisa Beckett",
+ primaryText = {
+ WearButtonText(
+ text = "Elisa Beckett",
+ textAlign = TextAlign.Start,
+ )
+ },
onClick = { },
- secondaryLabel = "beckett_bakery@gmail.com",
+ secondaryText = {
+ WearSecondaryLabel(
+ text = "beckett_bakery@gmail.com",
+ color = WearMaterialTheme.colors.onSurfaceVariant
+ )
+ },
icon = null,
)
}
@@ -166,8 +141,13 @@
@Composable
fun SignInOptionsChip(onClick: () -> Unit) {
CredentialsScreenChip(
- label = stringResource(R.string.dialog_sign_in_options_button),
- textAlign = TextAlign.Start,
+ primaryText = {
+ WearButtonText(
+ text = stringResource(R.string.dialog_sign_in_options_button),
+ textAlign = TextAlign.Center,
+ maxLines = 2
+ )
+ },
onClick = onClick,
)
}
@@ -182,7 +162,7 @@
fun ContinueChip(onClick: () -> Unit) {
CredentialsScreenChip(
onClick = onClick,
- text = {
+ primaryText = {
WearButtonText(
text = stringResource(R.string.dialog_continue_button),
textAlign = TextAlign.Center,
@@ -202,14 +182,21 @@
@Composable
fun DismissChip(onClick: () -> Unit) {
CredentialsScreenChip(
- label = stringResource(R.string.dialog_dismiss_button),
+ primaryText = {
+ WearButtonText(
+ text = stringResource(R.string.dialog_dismiss_button),
+ textAlign = TextAlign.Center,
+ maxLines = 2
+ )
+ },
onClick = onClick,
)
}
@Composable
fun LockedProviderChip(
authenticationEntryInfo: AuthenticationEntryInfo,
- onClick: () -> Unit
+ secondaryMaxLines: Int = 1,
+ onClick: () -> Unit,
) {
val secondaryLabel = stringResource(
if (authenticationEntryInfo.isUnlockedAndEmpty)
@@ -218,10 +205,21 @@
)
CredentialsScreenChip(
- label = authenticationEntryInfo.title,
+ primaryText = {
+ WearButtonText(
+ text = authenticationEntryInfo.title,
+ textAlign = TextAlign.Start,
+ maxLines = 2,
+ )
+ },
icon = authenticationEntryInfo.icon,
- secondaryLabel = secondaryLabel,
- textAlign = TextAlign.Start,
+ secondaryText = {
+ WearSecondaryLabel(
+ text = secondaryLabel,
+ color = WearMaterialTheme.colors.onSurfaceVariant,
+ maxLines = secondaryMaxLines
+ )
+ },
isAuthenticationEntryLocked = !authenticationEntryInfo.isUnlockedAndEmpty,
onClick = onClick,
)
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt
index a7b13ad..a1dc568 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt
@@ -16,7 +16,6 @@
package com.android.credentialmanager.common.ui.components
-import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.Text
@@ -93,15 +92,16 @@
fun WearSecondaryLabel(
text: String,
color: Color = WearMaterialTheme.colors.onSurface,
- modifier: Modifier = Modifier
+ modifier: Modifier = Modifier,
+ maxLines: Int = 1,
) {
Text(
- modifier = modifier.fillMaxSize(),
+ modifier = modifier.wrapContentSize(),
text = text,
color = color,
style = WearMaterialTheme.typography.caption1,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Start,
- maxLines = 1,
+ maxLines = maxLines,
)
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
index ef32c94..932b345 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
@@ -24,13 +24,13 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.Alignment
import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry
import com.android.credentialmanager.FlowEngine
import com.android.credentialmanager.R
import com.android.credentialmanager.common.ui.components.WearButtonText
+import com.android.credentialmanager.ui.components.LockedProviderChip
import com.android.credentialmanager.common.ui.components.WearSecondaryLabel
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.ui.components.CredentialsScreenChipSpacer
@@ -38,6 +38,8 @@
import com.google.android.horologist.compose.layout.ScalingLazyColumn
import com.google.android.horologist.compose.layout.rememberColumnState
import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults
+import androidx.compose.ui.text.style.TextAlign
+import androidx.wear.compose.material.MaterialTheme as WearMaterialTheme
/**
* Screen that shows multiple credentials to select from, grouped by accounts
@@ -69,6 +71,7 @@
text = stringResource(R.string.sign_in_options_title),
textAlign = TextAlign.Center,
modifier = Modifier.weight(0.854f).fillMaxSize(),
+ maxLines = 2,
)
Spacer(Modifier.weight(0.073f)) // 7.3% side margin
}
@@ -94,19 +97,39 @@
userNameEntries.sortedCredentialEntryList.forEach { credential: CredentialEntryInfo ->
item {
CredentialsScreenChip(
- label = credential.userName,
+ primaryText = {
+ WearButtonText(
+ text = credential.userName,
+ textAlign = TextAlign.Start,
+ maxLines = 2,
+ )
+ },
onClick = { selectEntry(credential, false) },
- secondaryLabel =
- credential.credentialTypeDisplayName.ifEmpty {
- credential.providerDisplayName
+ secondaryText =
+ {
+ WearSecondaryLabel(
+ text = credential.credentialTypeDisplayName.ifEmpty {
+ credential.providerDisplayName
+ },
+ color = WearMaterialTheme.colors.onSurfaceVariant,
+ maxLines = 2
+ )
},
icon = credential.icon,
- textAlign = TextAlign.Start
)
CredentialsScreenChipSpacer()
}
}
+
+ credentialSelectorUiState.authenticationEntryList.forEach { authenticationEntryInfo ->
+ item {
+ LockedProviderChip(authenticationEntryInfo, secondaryMaxLines = 2) {
+ selectEntry(authenticationEntryInfo, false)
+ }
+ CredentialsScreenChipSpacer()
+ }
+ }
}
if (credentialSelectorUiState.actionEntryList.isNotEmpty()) {
@@ -120,7 +143,8 @@
bottom = 4.dp,
start = 0.dp,
end = 0.dp
- ).fillMaxWidth(0.87f)
+ ).fillMaxWidth(0.87f),
+ maxLines = 2
)
Spacer(Modifier.weight(0.0624f)) // 6.24% side margin
}
@@ -128,9 +152,15 @@
credentialSelectorUiState.actionEntryList.forEach { actionEntry ->
item {
CredentialsScreenChip(
- label = actionEntry.title,
+ primaryText = {
+ WearButtonText(
+ text = actionEntry.title,
+ textAlign = TextAlign.Start,
+ maxLines = 2
+ )
+ },
onClick = { selectEntry(actionEntry, false) },
- secondaryLabel = null,
+ secondaryText = null,
icon = actionEntry.icon,
)
CredentialsScreenChipSpacer()
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
index 38307b0..b56b982b 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
@@ -17,7 +17,6 @@
package com.android.credentialmanager.ui.screens.multiple
import androidx.compose.foundation.layout.Row
-import androidx.compose.ui.text.style.TextAlign
import androidx.compose.foundation.layout.fillMaxSize
import com.android.credentialmanager.R
import androidx.compose.ui.res.stringResource
@@ -40,6 +39,10 @@
import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.ui.components.BottomSpacer
import com.android.credentialmanager.ui.components.CredentialsScreenChipSpacer
+import com.android.credentialmanager.common.ui.components.WearButtonText
+import com.android.credentialmanager.common.ui.components.WearSecondaryLabel
+import androidx.compose.ui.text.style.TextAlign
+import androidx.wear.compose.material.MaterialTheme as WearMaterialTheme
/**
* Screen that shows multiple credentials to select from.
@@ -82,14 +85,25 @@
credentials.forEach { credential: CredentialEntryInfo ->
item {
CredentialsScreenChip(
- label = credential.userName,
+ primaryText =
+ {
+ WearButtonText(
+ text = credential.userName,
+ textAlign = TextAlign.Start,
+ maxLines = 2
+ )
+ },
onClick = { selectEntry(credential, false) },
- secondaryLabel =
- credential.credentialTypeDisplayName.ifEmpty {
- credential.providerDisplayName
+ secondaryText = {
+ WearSecondaryLabel(
+ text = credential.credentialTypeDisplayName.ifEmpty {
+ credential.providerDisplayName
+ },
+ color = WearMaterialTheme.colors.onSurfaceVariant,
+ maxLines = 1 // See b/359649621 for context
+ )
},
icon = credential.icon,
- textAlign = TextAlign.Start
)
CredentialsScreenChipSpacer()
}
diff --git a/packages/PrintSpooler/res/values-zh-rCN/strings.xml b/packages/PrintSpooler/res/values-zh-rCN/strings.xml
index 3c95fd8..94d835b 100644
--- a/packages/PrintSpooler/res/values-zh-rCN/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rCN/strings.xml
@@ -29,7 +29,7 @@
<string name="label_pages" msgid="7768589729282182230">"页数"</string>
<string name="destination_default_text" msgid="5422708056807065710">"选择打印机"</string>
<string name="template_all_pages" msgid="3322235982020148762">"全部<xliff:g id="PAGE_COUNT">%1$s</xliff:g>页"</string>
- <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g>页"</string>
+ <string name="template_page_range" msgid="428638530038286328">"范围 <xliff:g id="PAGE_COUNT">%1$s</xliff:g>页"</string>
<string name="pages_range_example" msgid="8558694453556945172">"例如:1-5、8、11-13"</string>
<string name="print_preview" msgid="8010217796057763343">"打印预览"</string>
<string name="install_for_print_preview" msgid="6366303997385509332">"安装 PDF 查看器以便预览"</string>
diff --git a/packages/PrintSpooler/res/values-zh-rTW/strings.xml b/packages/PrintSpooler/res/values-zh-rTW/strings.xml
index 10932be..5ca4579 100644
--- a/packages/PrintSpooler/res/values-zh-rTW/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rTW/strings.xml
@@ -29,7 +29,7 @@
<string name="label_pages" msgid="7768589729282182230">"頁面"</string>
<string name="destination_default_text" msgid="5422708056807065710">"選取印表機"</string>
<string name="template_all_pages" msgid="3322235982020148762">"全部 <xliff:g id="PAGE_COUNT">%1$s</xliff:g> 頁"</string>
- <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> 頁"</string>
+ <string name="template_page_range" msgid="428638530038286328">"範圍是 <xliff:g id="PAGE_COUNT">%1$s</xliff:g> 頁"</string>
<string name="pages_range_example" msgid="8558694453556945172">"例如:1—5,8,11—13"</string>
<string name="print_preview" msgid="8010217796057763343">"列印預覽"</string>
<string name="install_for_print_preview" msgid="6366303997385509332">"安裝預覽所需的 PDF 檢視器"</string>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2e98c1f..f98b29a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -484,6 +484,7 @@
<activity android:name=".touchpad.tutorial.ui.view.TouchpadTutorialActivity"
android:exported="true"
+ android:showForAllUsers="true"
android:screenOrientation="userLandscape"
android:theme="@style/Theme.AppCompat.NoActionBar">
<intent-filter>
@@ -494,6 +495,7 @@
<activity android:name=".inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity"
android:exported="true"
+ android:showForAllUsers="true"
android:screenOrientation="userLandscape"
android:theme="@style/Theme.AppCompat.NoActionBar">
<intent-filter>
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 7c0db8d..0b364ac 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -541,6 +541,16 @@
}
flag {
+ name: "clipboard_shared_transitions"
+ namespace: "systemui"
+ description: "Show shared transitions from clipboard"
+ bug: "360843770"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "clipboard_image_timeout"
namespace: "systemui"
description: "Wait for clipboard image to load before showing UI"
@@ -1346,3 +1356,14 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "face_message_defer_update"
+ namespace: "systemui"
+ description: "Only analyze the last n frames when determining whether to defer a face auth help message like low light"
+ bug: "351863611"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 0bef05dc..9e292d0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -68,7 +68,6 @@
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
@@ -82,7 +81,6 @@
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.thenIf
-import com.android.internal.policy.SystemBarUtils
import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.res.R
@@ -137,14 +135,16 @@
.notificationHeadsUpHeight(stackScrollView)
.debugBackground(viewModel, DEBUG_HUN_COLOR)
.onGloballyPositioned { coordinates: LayoutCoordinates ->
+ val positionInWindow = coordinates.positionInWindow()
val boundsInWindow = coordinates.boundsInWindow()
debugLog(viewModel) {
"HUNS onGloballyPositioned:" +
" size=${coordinates.size}" +
" bounds=$boundsInWindow"
}
- // Note: boundsInWindow doesn't scroll off the screen
- stackScrollView.setHeadsUpTop(boundsInWindow.top)
+ // Note: boundsInWindow doesn't scroll off the screen, so use positionInWindow
+ // for top bound, which can scroll off screen while snoozing
+ stackScrollView.setHeadsUpTop(positionInWindow.y)
stackScrollView.setHeadsUpBottom(boundsInWindow.bottom)
}
)
@@ -159,16 +159,10 @@
stackScrollView: NotificationScrollView,
viewModel: NotificationsPlaceholderViewModel,
) {
- val context = LocalContext.current
- val density = LocalDensity.current
- val statusBarHeight = SystemBarUtils.getStatusBarHeight(context)
- val headsUpPadding =
- with(density) { dimensionResource(id = R.dimen.heads_up_status_bar_padding).roundToPx() }
-
val isHeadsUp by viewModel.isHeadsUpOrAnimatingAway.collectAsStateWithLifecycle(false)
var scrollOffset by remember { mutableFloatStateOf(0f) }
- val minScrollOffset = -(statusBarHeight + headsUpPadding.toFloat())
+ val minScrollOffset = -(stackScrollView.getHeadsUpInset().toFloat())
val maxScrollOffset = 0f
val scrollableState = rememberScrollableState { delta ->
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index cfe0bec..15a4a40 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -17,6 +17,8 @@
package com.android.systemui.bouncer.domain.interactor
import android.content.pm.UserInfo
+import android.hardware.biometrics.BiometricFaceConstants
+import android.hardware.biometrics.BiometricSourceType
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -25,6 +27,7 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Pattern
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FaceSensorInfo
@@ -58,7 +61,9 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -77,6 +82,8 @@
@Mock private lateinit var securityModel: KeyguardSecurityModel
@Mock private lateinit var countDownTimerUtil: CountDownTimerUtil
@Mock private lateinit var systemPropertiesHelper: SystemPropertiesHelper
+ @Captor
+ private lateinit var keyguardUpdateMonitorCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
private lateinit var underTest: BouncerMessageInteractor
@@ -207,6 +214,52 @@
}
@Test
+ fun resetMessageBackToDefault_faceAuthRestarts() =
+ testScope.runTest {
+ init()
+ verify(updateMonitor).registerCallback(keyguardUpdateMonitorCaptor.capture())
+ val bouncerMessage by collectLastValue(underTest.bouncerMessage)
+
+ underTest.setFaceAcquisitionMessage("not empty")
+
+ assertThat(primaryResMessage(bouncerMessage))
+ .isEqualTo("Unlock with PIN or fingerprint")
+ assertThat(bouncerMessage?.secondaryMessage?.message).isEqualTo("not empty")
+
+ keyguardUpdateMonitorCaptor.value.onBiometricAcquired(
+ BiometricSourceType.FACE,
+ BiometricFaceConstants.FACE_ACQUIRED_START
+ )
+
+ assertThat(primaryResMessage(bouncerMessage))
+ .isEqualTo("Unlock with PIN or fingerprint")
+ assertThat(bouncerMessage?.secondaryMessage?.message).isNull()
+ }
+
+ @Test
+ fun faceRestartDoesNotResetFingerprintMessage() =
+ testScope.runTest {
+ init()
+ verify(updateMonitor).registerCallback(keyguardUpdateMonitorCaptor.capture())
+ val bouncerMessage by collectLastValue(underTest.bouncerMessage)
+
+ underTest.setFingerprintAcquisitionMessage("not empty")
+
+ assertThat(primaryResMessage(bouncerMessage))
+ .isEqualTo("Unlock with PIN or fingerprint")
+ assertThat(bouncerMessage?.secondaryMessage?.message).isEqualTo("not empty")
+
+ keyguardUpdateMonitorCaptor.value.onBiometricAcquired(
+ BiometricSourceType.FACE,
+ BiometricFaceConstants.FACE_ACQUIRED_START
+ )
+
+ assertThat(primaryResMessage(bouncerMessage))
+ .isEqualTo("Unlock with PIN or fingerprint")
+ assertThat(bouncerMessage?.secondaryMessage?.message).isEqualTo("not empty")
+ }
+
+ @Test
fun setFingerprintMessage_propagateValue() =
testScope.runTest {
init()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
index c5518b0..2ba4bf9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
@@ -36,6 +36,7 @@
import com.android.systemui.keyguard.data.repository.realKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -834,4 +835,86 @@
)
)
}
+
+ /**
+ * KTF: LOCKSCREEN -> ALTERNATE_BOUNCER starts but then STL: GLANCEABLE_HUB -> BLANK interrupts.
+ *
+ * Verifies that we correctly cancel the previous KTF state before starting the glanceable hub
+ * transition.
+ */
+ @Test
+ fun transition_to_blank_after_ktf_started_another_transition() =
+ testScope.runTest {
+ // Another transition has already started to the alternate bouncer.
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ from = LOCKSCREEN,
+ to = ALTERNATE_BOUNCER,
+ animator = null,
+ ownerName = "external",
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ ),
+ )
+
+ val allSteps by collectValues(keyguardTransitionRepository.transitions)
+ // Keep track of existing size to drop any pre-existing steps that we don't
+ // care about.
+ val numToDrop = allSteps.size
+
+ sceneTransitions.value = hubToBlank
+ runCurrent()
+ progress.emit(0.4f)
+ runCurrent()
+ // We land on blank.
+ sceneTransitions.value = Idle(CommunalScenes.Blank)
+
+ // We should cancel the previous ALTERNATE_BOUNCER transition and transition back
+ // to the GLANCEABLE_HUB before we can transition away from it.
+ assertThat(allSteps.drop(numToDrop))
+ .containsExactly(
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = ALTERNATE_BOUNCER,
+ transitionState = CANCELED,
+ value = 0f,
+ ownerName = "external",
+ ),
+ TransitionStep(
+ from = ALTERNATE_BOUNCER,
+ to = GLANCEABLE_HUB,
+ transitionState = STARTED,
+ value = 1f,
+ ownerName = ownerName,
+ ),
+ TransitionStep(
+ from = ALTERNATE_BOUNCER,
+ to = GLANCEABLE_HUB,
+ transitionState = FINISHED,
+ value = 1f,
+ ownerName = ownerName,
+ ),
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = LOCKSCREEN,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = ownerName,
+ ),
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = LOCKSCREEN,
+ transitionState = RUNNING,
+ value = 0.4f,
+ ownerName = ownerName,
+ ),
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = LOCKSCREEN,
+ transitionState = FINISHED,
+ value = 1f,
+ ownerName = ownerName,
+ ),
+ )
+ .inOrder()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 1bc2e24..5a6f2be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1968,47 +1968,6 @@
@Test
@DisableSceneContainer
- @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun glanceableHubToLockscreen_communalKtfRefactor() =
- testScope.runTest {
- // GIVEN a prior transition has run to GLANCEABLE_HUB
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- runCurrent()
- clearInvocations(transitionRepository)
-
- // WHEN a transition away from glanceable hub starts
- val currentScene = CommunalScenes.Communal
- val targetScene = CommunalScenes.Blank
-
- val progress = MutableStateFlow(0f)
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Transition(
- fromScene = currentScene,
- toScene = targetScene,
- currentScene = flowOf(targetScene),
- progress = progress,
- isInitiatedByUserInput = false,
- isUserInputOngoing = flowOf(false),
- )
- )
- communalSceneInteractor.setTransitionState(transitionState)
- progress.value = .1f
- runCurrent()
-
- assertThat(transitionRepository)
- .startedTransition(
- ownerName = CommunalSceneTransitionInteractor::class.simpleName,
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.LOCKSCREEN,
- animatorAssertion = { it.isNull() }, // transition should be manually animated
- )
-
- coroutineContext.cancelChildren()
- }
-
- @Test
- @DisableSceneContainer
@DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun glanceableHubToDozing() =
testScope.runTest {
@@ -2260,54 +2219,6 @@
}
@Test
- @DisableSceneContainer
- @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun glanceableHubToDreaming_communalKtfRefactor() =
- testScope.runTest {
- // GIVEN that we are dreaming and not dozing
- powerInteractor.setAwakeForTest()
- keyguardRepository.setDreaming(true)
- keyguardRepository.setDreamingWithOverlay(true)
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
- )
- advanceTimeBy(600L)
-
- // GIVEN a prior transition has run to GLANCEABLE_HUB
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- runCurrent()
- clearInvocations(transitionRepository)
-
- // WHEN a transition away from glanceable hub starts
- val currentScene = CommunalScenes.Communal
- val targetScene = CommunalScenes.Blank
-
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Transition(
- fromScene = currentScene,
- toScene = targetScene,
- currentScene = flowOf(targetScene),
- progress = flowOf(0f, 0.1f),
- isInitiatedByUserInput = false,
- isUserInputOngoing = flowOf(false),
- )
- )
- communalSceneInteractor.setTransitionState(transitionState)
- runCurrent()
-
- assertThat(transitionRepository)
- .startedTransition(
- ownerName = CommunalSceneTransitionInteractor::class.simpleName,
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.DREAMING,
- animatorAssertion = { it.isNull() }, // transition should be manually animated
- )
-
- coroutineContext.cancelChildren()
- }
-
- @Test
@BrokenWithSceneContainer(339465026)
@EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun glanceableHubToOccludedDoesNotTriggerWhenDreamStateChanges_communalKtfRefactor() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 6dbe94b..a407468 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -360,6 +360,25 @@
}
@Test
+ fun alpha_transitionBetweenHubAndDream_isZero() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha(viewState))
+
+ // Default value check
+ assertThat(alpha).isEqualTo(1f)
+
+ // Start transitioning between DREAM and HUB but don't finish.
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ throughTransitionState = TransitionState.STARTED,
+ )
+
+ assertThat(alpha).isEqualTo(0f)
+ }
+
+ @Test
@EnableSceneContainer
fun alpha_transitionToHub_isZero_scene_container() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
index 5999265..768fbca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -31,6 +31,8 @@
import com.android.systemui.qs.fgsManagerController
import com.android.systemui.res.R
import com.android.systemui.shade.largeScreenHeaderHelper
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.sysuiStatusBarStateController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
@@ -140,6 +142,42 @@
}
}
+ @Test
+ fun statusBarState_followsController() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ val statusBarState by collectLastValue(underTest.statusBarState)
+ runCurrent()
+
+ sysuiStatusBarStateController.setState(StatusBarState.SHADE)
+ assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
+
+ sysuiStatusBarStateController.setState(StatusBarState.KEYGUARD)
+ assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
+
+ sysuiStatusBarStateController.setState(StatusBarState.SHADE_LOCKED)
+ assertThat(statusBarState).isEqualTo(StatusBarState.SHADE_LOCKED)
+ }
+ }
+
+ @Test
+ fun statusBarState_changesEarlyIfUpcomingStateIsKeyguard() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ val statusBarState by collectLastValue(underTest.statusBarState)
+
+ sysuiStatusBarStateController.setState(StatusBarState.SHADE)
+ sysuiStatusBarStateController.setUpcomingState(StatusBarState.SHADE_LOCKED)
+ assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
+
+ sysuiStatusBarStateController.setUpcomingState(StatusBarState.KEYGUARD)
+ assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
+
+ sysuiStatusBarStateController.setUpcomingState(StatusBarState.SHADE)
+ assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
+ }
+ }
+
private inline fun TestScope.testWithinLifecycle(
crossinline block: suspend TestScope.() -> TestResult
): TestResult {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index b30861c..c633816 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -58,6 +58,7 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
import com.android.systemui.scene.domain.startable.sceneContainerStartable
+import com.android.systemui.scene.shared.logger.sceneLogger
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
@@ -133,6 +134,7 @@
sceneInteractor = sceneInteractor,
falsingInteractor = kosmos.falsingInteractor,
powerInteractor = kosmos.powerInteractor,
+ logger = kosmos.sceneLogger,
motionEventHandlerReceiver = {},
)
.apply { setTransitionState(transitionState) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 64a13de..8f8d2e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -1682,6 +1682,7 @@
underTest.start()
// run all pending dismiss succeeded/cancelled calls from setup:
+ runCurrent()
kosmos.fakeExecutor.runAllReady()
val dismissCallback: IKeyguardDismissCallback = mock()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index b315783..f856c55 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.logger.sceneLogger
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
@@ -72,6 +73,7 @@
sceneInteractor = sceneInteractor,
falsingInteractor = kosmos.falsingInteractor,
powerInteractor = kosmos.powerInteractor,
+ logger = kosmos.sceneLogger,
motionEventHandlerReceiver = { motionEventHandler ->
this@SceneContainerViewModelTest.motionEventHandler = motionEventHandler
},
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
index eac86e5..ce9b3be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
@@ -23,7 +23,6 @@
import android.os.Handler
import android.testing.TestableLooper
import android.view.View
-import android.widget.FrameLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -46,6 +45,8 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.never
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -67,12 +68,9 @@
@Mock private lateinit var session: SmartspaceSession
- private lateinit var controller: CommunalSmartspaceController
+ private val preconditionListenerCaptor = argumentCaptor<SmartspacePrecondition.Listener>()
- // TODO(b/272811280): Remove usage of real view
- private val fakeParent by lazy {
- FrameLayout(context)
- }
+ private lateinit var controller: CommunalSmartspaceController
/**
* A class which implements SmartspaceView and extends View. This is mocked to provide the right
@@ -155,6 +153,26 @@
verify(session).close()
}
+ /** Ensures smartspace session begins when precondition is met if there is any listener. */
+ @Test
+ fun testConnectOnPreconditionMet() {
+ // Precondition not met
+ `when`(precondition.conditionsMet()).thenReturn(false)
+ controller.addListener(listener)
+
+ // Verify session not created because precondition not met
+ verify(smartspaceManager, never()).createSmartspaceSession(any())
+
+ // Precondition met
+ `when`(precondition.conditionsMet()).thenReturn(true)
+ verify(precondition).addListener(preconditionListenerCaptor.capture())
+ val preconditionListener = preconditionListenerCaptor.firstValue
+ preconditionListener.onCriteriaChanged()
+
+ // Verify session created
+ verify(smartspaceManager).createSmartspaceSession(any())
+ }
+
/**
* Ensures session is closed and weather plugin unregisters the notifier when weather smartspace
* view is detached.
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
index 094dc0a..e939f37 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.data.repository
+import android.hardware.biometrics.BiometricSourceType
import com.android.systemui.bouncer.shared.model.BouncerMessageModel
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
@@ -26,7 +27,12 @@
interface BouncerMessageRepository {
val bouncerMessage: Flow<BouncerMessageModel>
- fun setMessage(message: BouncerMessageModel)
+ fun setMessage(message: BouncerMessageModel, source: BiometricSourceType? = null)
+
+ /**
+ * Return the source of the current [bouncerMessage] if it was set from a biometric. Else, null.
+ */
+ fun getMessageSource(): BiometricSourceType?
}
@SysUISingleton
@@ -34,8 +40,14 @@
private val _bouncerMessage = MutableStateFlow(BouncerMessageModel())
override val bouncerMessage: Flow<BouncerMessageModel> = _bouncerMessage
+ private var messageSource: BiometricSourceType? = null
- override fun setMessage(message: BouncerMessageModel) {
+ override fun setMessage(message: BouncerMessageModel, source: BiometricSourceType?) {
_bouncerMessage.value = message
+ messageSource = source
+ }
+
+ override fun getMessageSource(): BiometricSourceType? {
+ return messageSource
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
index 8e12646..85fffbf 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.domain.interactor
+import android.hardware.biometrics.BiometricFaceConstants
import android.hardware.biometrics.BiometricSourceType
import android.os.CountDownTimer
import com.android.keyguard.KeyguardSecurityModel
@@ -115,7 +116,8 @@
isFingerprintAuthCurrentlyAllowedOnBouncer.value
)
.toMessage()
- }
+ },
+ biometricSourceType,
)
}
@@ -123,7 +125,12 @@
biometricSourceType: BiometricSourceType?,
acquireInfo: Int
) {
- super.onBiometricAcquired(biometricSourceType, acquireInfo)
+ if (
+ repository.getMessageSource() == BiometricSourceType.FACE &&
+ acquireInfo == BiometricFaceConstants.FACE_ACQUIRED_START
+ ) {
+ repository.setMessage(defaultMessage)
+ }
}
override fun onBiometricAuthenticated(
@@ -131,7 +138,7 @@
biometricSourceType: BiometricSourceType?,
isStrongBiometric: Boolean
) {
- repository.setMessage(defaultMessage)
+ repository.setMessage(defaultMessage, biometricSourceType)
}
}
@@ -291,7 +298,8 @@
currentSecurityMode,
value,
isFingerprintAuthCurrentlyAllowedOnBouncer.value
- )
+ ),
+ BiometricSourceType.FINGERPRINT,
)
}
@@ -302,7 +310,8 @@
currentSecurityMode,
value,
isFingerprintAuthCurrentlyAllowedOnBouncer.value
- )
+ ),
+ BiometricSourceType.FACE,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 040af90..aabfbd1 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -20,6 +20,7 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
import static com.android.systemui.Flags.clipboardImageTimeout;
+import static com.android.systemui.Flags.clipboardSharedTransitions;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
@@ -33,7 +34,6 @@
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
-import static com.android.systemui.flags.Flags.CLIPBOARD_SHARED_TRANSITIONS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -207,7 +207,7 @@
mClipboardUtils = clipboardUtils;
mBgExecutor = bgExecutor;
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
mView.setCallbacks(this);
} else {
mView.setCallbacks(mClipboardCallbacks);
@@ -220,7 +220,7 @@
});
mTimeoutHandler.setOnTimeoutRunnable(() -> {
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
finish(CLIPBOARD_OVERLAY_TIMED_OUT);
} else {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT);
@@ -232,7 +232,7 @@
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
finish(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
} else {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
@@ -248,7 +248,7 @@
@Override
public void onReceive(Context context, Intent intent) {
if (SCREENSHOT_ACTION.equals(intent.getAction())) {
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
finish(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
} else {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
@@ -481,7 +481,7 @@
remoteAction.ifPresent(action -> {
mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
mView.post(() -> mView.setActionChip(action, () -> {
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
finish(CLIPBOARD_OVERLAY_ACTION_TAPPED);
} else {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
@@ -528,7 +528,7 @@
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
if (!mView.isInTouchRegion(
(int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
finish(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
} else {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
@@ -575,6 +575,10 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
+ // check again after animation to see if we should still be minimized
+ if (mIsMinimized && !shouldShowMinimized(mWindow.getWindowInsets())) {
+ animateFromMinimized();
+ }
if (mOnUiUpdate != null) {
mOnUiUpdate.run();
}
@@ -690,14 +694,14 @@
@Override
public void onDismissButtonTapped() {
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
finish(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
}
}
@Override
public void onRemoteCopyButtonTapped() {
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
finish(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED,
IntentCreator.getRemoteCopyIntent(mClipboardModel.getClipData(), mContext));
}
@@ -705,7 +709,7 @@
@Override
public void onShareButtonTapped() {
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
if (mClipboardModel.getType() != ClipboardModel.Type.OTHER) {
finishWithSharedTransition(CLIPBOARD_OVERLAY_SHARE_TAPPED,
IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext));
@@ -715,7 +719,7 @@
@Override
public void onPreviewTapped() {
- if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+ if (clipboardSharedTransitions()) {
switch (mClipboardModel.getType()) {
case TEXT:
finish(CLIPBOARD_OVERLAY_EDIT_TAPPED,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
index 6343752..5bbb46d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
@@ -223,19 +223,15 @@
// KTF for this case and just collect the new progress instead.
collectProgress(transition)
} else if (transition.toScene == CommunalScenes.Communal) {
- if (currentTransitionId != null) {
- if (currentToState == KeyguardState.GLANCEABLE_HUB) {
- transitionKtfTo(transitionInteractor.getStartedFromState())
- }
+ if (currentToState == KeyguardState.GLANCEABLE_HUB) {
+ transitionKtfTo(transitionInteractor.getStartedFromState())
}
startTransitionToGlanceableHub()
collectProgress(transition)
} else if (transition.toScene == CommunalScenes.Blank) {
- if (currentTransitionId != null) {
- // Another transition started before this one is completed. Transition to the
- // GLANCEABLE_HUB state so that we can properly transition away from it.
- transitionKtfTo(KeyguardState.GLANCEABLE_HUB)
- }
+ // Another transition started before this one is completed. Transition to the
+ // GLANCEABLE_HUB state so that we can properly transition away from it.
+ transitionKtfTo(KeyguardState.GLANCEABLE_HUB)
startTransitionFromGlanceableHub()
collectProgress(transition)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
index 80db535..012c844 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
@@ -65,6 +65,12 @@
var preconditionListener =
object : SmartspacePrecondition.Listener {
override fun onCriteriaChanged() {
+ if (session == null && hasActiveSessionListeners()) {
+ Log.d(TAG, "Precondition criteria changed. Attempting to connect session.")
+ connectSession()
+ return
+ }
+
reloadSmartspace()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e5f3a57..4d75d66 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -337,10 +337,6 @@
// TODO(b/278714186) Tracking Bug
@JvmField
val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag("clipboard_image_timeout", teamfood = true)
- // TODO(b/279405451): Tracking Bug
- @JvmField
- val CLIPBOARD_SHARED_TRANSITIONS =
- unreleasedFlag("clipboard_shared_transitions", teamfood = true)
// 1900
@JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
index 6bc640d..1aa5ee0 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
@@ -20,7 +20,6 @@
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -97,7 +96,6 @@
val secondaryFixedDim = LocalAndroidColorScheme.current.secondaryFixedDim
val onSecondaryFixed = LocalAndroidColorScheme.current.onSecondaryFixed
val onSecondaryFixedVariant = LocalAndroidColorScheme.current.onSecondaryFixedVariant
- val surfaceContainer = MaterialTheme.colorScheme.surfaceContainer
val dynamicProperties =
rememberLottieDynamicProperties(
rememberColorFilterProperty(".primaryFixedDim", primaryFixedDim),
@@ -106,11 +104,10 @@
rememberColorFilterProperty(".onSecondaryFixedVariant", onSecondaryFixedVariant)
)
val screenColors =
- remember(surfaceContainer, dynamicProperties) {
+ remember(dynamicProperties) {
TutorialScreenConfig.Colors(
background = onSecondaryFixed,
- successBackground = surfaceContainer,
- title = primaryFixedDim,
+ title = secondaryFixedDim,
animationColors = dynamicProperties,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
index c50b7dc..b271356 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
@@ -24,13 +24,13 @@
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -46,7 +46,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.res.stringResource
@@ -60,6 +59,7 @@
import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition
import com.airbnb.lottie.compose.rememberLottieDynamicProperty
+import com.android.compose.modifiers.background
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.FINISHED
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.IN_PROGRESS
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NOT_STARTED
@@ -76,19 +76,11 @@
onDoneButtonClicked: () -> Unit,
config: TutorialScreenConfig
) {
- val animatedColor by
- animateColorAsState(
- targetValue =
- if (actionState == FINISHED) config.colors.successBackground
- else config.colors.background,
- animationSpec = tween(durationMillis = 150, easing = LinearEasing),
- label = "backgroundColor"
- )
Column(
verticalArrangement = Arrangement.Center,
modifier =
Modifier.fillMaxSize()
- .drawBehind { drawRect(animatedColor) }
+ .background(config.colors.background)
.padding(start = 48.dp, top = 124.dp, end = 48.dp, bottom = 48.dp)
) {
Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt
index 0406bb9..55e5f2d 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt
@@ -29,7 +29,6 @@
data class Colors(
val background: Color,
- val successBackground: Color,
val title: Color,
val animationColors: LottieDynamicProperties
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 06f77bf..54964d6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -33,6 +33,8 @@
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
@@ -45,6 +47,7 @@
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.kotlin.BooleanFlowOperators.any
import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.sample
@@ -198,29 +201,37 @@
.distinctUntilChanged()
/**
- * Keyguard should not show while the communal hub is fully visible. This check is added since
- * at the moment, closing the notification shade will cause the keyguard alpha to be set back to
- * 1. Also ensure keyguard is never visible when GONE.
+ * Keyguard states which should fully hide the keyguard.
+ *
+ * Note: [GONE] is not included as it is handled separately.
+ */
+ private val hiddenKeyguardStates = listOf(OCCLUDED, DREAMING, GLANCEABLE_HUB)
+
+ /**
+ * Keyguard should not show if fully transitioned into a hidden keyguard state or if
+ * transitioning between hidden states.
*/
private val hideKeyguard: Flow<Boolean> =
- combine(
- communalInteractor.isIdleOnCommunal,
+ (hiddenKeyguardStates.map { state ->
keyguardTransitionInteractor
- .transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
+ .transitionValue(state)
.map { it == 1f }
- .onStart { emit(false) },
- keyguardTransitionInteractor
- .transitionValue(OCCLUDED)
- .map { it == 1f }
- .onStart { emit(false) },
- keyguardTransitionInteractor
- .transitionValue(KeyguardState.DREAMING)
- .map { it == 1f }
- .onStart { emit(false) },
- ) { isIdleOnCommunal, isGone, isOccluded, isDreaming ->
- isIdleOnCommunal || isGone || isOccluded || isDreaming
- }
- .distinctUntilChanged()
+ .onStart { emit(false) }
+ } +
+ listOf(
+ communalInteractor.isIdleOnCommunal,
+ keyguardTransitionInteractor
+ .transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
+ .map { it == 1f }
+ .onStart { emit(false) },
+ keyguardTransitionInteractor
+ .isInTransitionWhere(
+ fromStatePredicate = { hiddenKeyguardStates.contains(it) },
+ toStatePredicate = { hiddenKeyguardStates.contains(it) },
+ )
+ .onStart { emit(false) },
+ ))
+ .any()
/** Last point that the root view was tapped */
val lastRootViewTapPosition: Flow<Point?> = keyguardInteractor.lastRootViewTapPosition
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 9f9c8e9..3613c11 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -37,6 +37,8 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
@@ -50,6 +52,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.modifiers.height
import com.android.compose.modifiers.padding
+import com.android.compose.modifiers.thenIf
import com.android.compose.theme.PlatformTheme
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -59,6 +62,7 @@
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.ui.notificationScrimClip
import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel
import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -100,6 +104,17 @@
private val qqsPositionOnRoot = Rect()
private val composeViewPositionOnScreen = Rect()
+ // Inside object for namespacing
+ private val notificationScrimClippingParams =
+ object {
+ var isEnabled by mutableStateOf(false)
+ var leftInset by mutableStateOf(0)
+ var rightInset by mutableStateOf(0)
+ var top by mutableStateOf(0)
+ var bottom by mutableStateOf(0)
+ var radius by mutableStateOf(0)
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -126,7 +141,18 @@
AnimatedVisibility(
visible = visible,
- modifier = Modifier.windowInsetsPadding(WindowInsets.navigationBars)
+ modifier =
+ Modifier.windowInsetsPadding(WindowInsets.navigationBars).thenIf(
+ notificationScrimClippingParams.isEnabled
+ ) {
+ Modifier.notificationScrimClip(
+ notificationScrimClippingParams.leftInset,
+ notificationScrimClippingParams.top,
+ notificationScrimClippingParams.rightInset,
+ notificationScrimClippingParams.bottom,
+ notificationScrimClippingParams.radius,
+ )
+ }
) {
AnimatedContent(targetState = qsState) {
when (it) {
@@ -280,7 +306,16 @@
cornerRadius: Int,
visible: Boolean,
fullWidth: Boolean
- ) {}
+ ) {
+ notificationScrimClippingParams.isEnabled = visible
+ notificationScrimClippingParams.top = top
+ notificationScrimClippingParams.bottom = bottom
+ // Full width means that QS will show in the entire width allocated to it (for example
+ // phone) vs. showing in a narrower column (for example, tablet portrait).
+ notificationScrimClippingParams.leftInset = if (fullWidth) 0 else leftInset
+ notificationScrimClippingParams.rightInset = if (fullWidth) 0 else rightInset
+ notificationScrimClippingParams.radius = cornerRadius
+ }
override fun isFullyCollapsed(): Boolean {
return viewModel.qsExpansionValue <= 0f
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
new file mode 100644
index 0000000..93c6445
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.systemui.qs.composefragment.ui
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ClipOp
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.asAndroidPath
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.graphics.drawscope.clipPath
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.InspectorInfo
+
+/**
+ * Clipping modifier for clipping out the notification scrim as it slides over QS. It will clip out
+ * ([ClipOp.Difference]) a `RoundRect(-leftInset, top, width + rightInset, bottom, radius, radius)`
+ * from the QS container.
+ */
+fun Modifier.notificationScrimClip(
+ leftInset: Int,
+ top: Int,
+ rightInset: Int,
+ bottom: Int,
+ radius: Int
+): Modifier {
+ return this then NotificationScrimClipElement(leftInset, top, rightInset, bottom, radius)
+}
+
+private class NotificationScrimClipNode(
+ var leftInset: Float,
+ var top: Float,
+ var rightInset: Float,
+ var bottom: Float,
+ var radius: Float,
+) : DrawModifierNode, Modifier.Node() {
+ private val path = Path()
+
+ var invalidated = true
+
+ override fun ContentDrawScope.draw() {
+ if (invalidated) {
+ path.rewind()
+ path
+ .asAndroidPath()
+ .addRoundRect(
+ -leftInset,
+ top,
+ size.width + rightInset,
+ bottom,
+ radius,
+ radius,
+ android.graphics.Path.Direction.CW
+ )
+ invalidated = false
+ }
+ clipPath(path, ClipOp.Difference) { this@draw.drawContent() }
+ }
+}
+
+private data class NotificationScrimClipElement(
+ val leftInset: Int,
+ val top: Int,
+ val rightInset: Int,
+ val bottom: Int,
+ val radius: Int,
+) : ModifierNodeElement<NotificationScrimClipNode>() {
+ override fun create(): NotificationScrimClipNode {
+ return NotificationScrimClipNode(
+ leftInset.toFloat(),
+ top.toFloat(),
+ rightInset.toFloat(),
+ bottom.toFloat(),
+ radius.toFloat(),
+ )
+ }
+
+ override fun update(node: NotificationScrimClipNode) {
+ val changed =
+ node.leftInset != leftInset.toFloat() ||
+ node.top != top.toFloat() ||
+ node.rightInset != rightInset.toFloat() ||
+ node.bottom != bottom.toFloat() ||
+ node.radius != radius.toFloat()
+ if (changed) {
+ node.leftInset = leftInset.toFloat()
+ node.top = top.toFloat()
+ node.rightInset = rightInset.toFloat()
+ node.bottom = bottom.toFloat()
+ node.radius = radius.toFloat()
+ node.invalidated = true
+ }
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ name = "notificationScrimClip"
+ properties["leftInset"] = leftInset
+ properties["top"] = top
+ properties["rightInset"] = rightInset
+ properties["bottom"] = bottom
+ properties["radius"] = radius
+ }
+}
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 7d52216..4b1312c 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
@@ -19,21 +19,26 @@
import android.content.res.Resources
import android.graphics.Rect
import androidx.annotation.FloatRange
+import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LifecycleCoroutineScope
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
+import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.util.LargeScreenUtils
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -140,7 +145,34 @@
private val _keyguardAndExpanded = MutableStateFlow(false)
- private val _statusBarState = MutableStateFlow(-1)
+ /**
+ * Tracks the current [StatusBarState]. It will switch early if the upcoming state is
+ * [StatusBarState.KEYGUARD]
+ */
+ @get:VisibleForTesting
+ val statusBarState =
+ conflatedCallbackFlow {
+ val callback =
+ object : StatusBarStateController.StateListener {
+ override fun onStateChanged(newState: Int) {
+ trySend(newState)
+ }
+
+ override fun onUpcomingStateChanged(upcomingState: Int) {
+ if (upcomingState == StatusBarState.KEYGUARD) {
+ trySend(upcomingState)
+ }
+ }
+ }
+ sysuiStatusBarStateController.addCallback(callback)
+
+ awaitClose { sysuiStatusBarStateController.removeCallback(callback) }
+ }
+ .stateIn(
+ lifecycleScope,
+ SharingStarted.WhileSubscribed(),
+ sysuiStatusBarStateController.state,
+ )
private val _viewHeight = MutableStateFlow(0)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 6e7df7e..1b9c346 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -28,8 +28,6 @@
import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.util.kotlin.getValue
-import com.android.systemui.util.kotlin.pairwiseBy
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -83,20 +81,7 @@
* Note that during a transition between scenes, more than one scene might be rendered but only
* one is considered the committed/current scene.
*/
- val currentScene: StateFlow<SceneKey> =
- repository.currentScene
- .pairwiseBy(initialValue = repository.currentScene.value) { from, to ->
- logger.logSceneChangeCommitted(
- from = from,
- to = to,
- )
- to
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = repository.currentScene.value,
- )
+ val currentScene: StateFlow<SceneKey> = repository.currentScene
/**
* The current state of the transition.
@@ -242,14 +227,15 @@
return
}
- logger.logSceneChangeRequested(
+ onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(resolvedScene, sceneState) }
+
+ logger.logSceneChanged(
from = currentSceneKey,
to = resolvedScene,
reason = loggingReason,
isInstant = false,
)
- onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(resolvedScene, sceneState) }
repository.changeScene(resolvedScene, transitionKey)
}
@@ -282,7 +268,7 @@
return
}
- logger.logSceneChangeRequested(
+ logger.logSceneChanged(
from = currentSceneKey,
to = resolvedScene,
reason = loggingReason,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
index cf1518e..94c94e2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
@@ -43,7 +43,7 @@
)
}
- fun logSceneChangeRequested(
+ fun logSceneChanged(
from: SceneKey,
to: SceneKey,
reason: String,
@@ -60,7 +60,7 @@
},
messagePrinter = {
buildString {
- append("Scene change requested: $str1 → $str2")
+ append("Scene changed: $str1 → $str2")
if (isInstant) {
append(" (instant)")
}
@@ -70,21 +70,6 @@
)
}
- fun logSceneChangeCommitted(
- from: SceneKey,
- to: SceneKey,
- ) {
- logBuffer.log(
- tag = TAG,
- level = LogLevel.INFO,
- messageInitializer = {
- str1 = from.toString()
- str2 = to.toString()
- },
- messagePrinter = { "Scene change committed: $str1 → $str2" },
- )
- }
-
fun logSceneTransition(transitionState: ObservableTransitionState) {
when (transitionState) {
is ObservableTransitionState.Transition -> {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 7f7f0f1..f8a9f8c 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -27,6 +27,7 @@
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.Scenes
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -42,6 +43,7 @@
private val sceneInteractor: SceneInteractor,
private val falsingInteractor: FalsingInteractor,
private val powerInteractor: PowerInteractor,
+ private val logger: SceneLogger,
@Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
) : SysUiViewModel() {
/**
@@ -135,16 +137,29 @@
else -> null
}
- return interactionTypeOrNull?.let { interactionType ->
- // It's important that the falsing system is always queried, even if no enforcement will
- // occur. This helps build up the right signal in the system.
- val isFalseTouch = falsingInteractor.isFalseTouch(interactionType)
+ val fromScene = currentScene.value
+ val isAllowed =
+ interactionTypeOrNull?.let { interactionType ->
+ // It's important that the falsing system is always queried, even if no enforcement
+ // will occur. This helps build up the right signal in the system.
+ val isFalseTouch = falsingInteractor.isFalseTouch(interactionType)
- // Only enforce falsing if moving from the lockscreen scene to a new scene.
- val fromLockscreenScene = currentScene.value == Scenes.Lockscreen
+ // Only enforce falsing if moving from the lockscreen scene to a new scene.
+ val fromLockscreenScene = fromScene == Scenes.Lockscreen
- !fromLockscreenScene || !isFalseTouch
- } ?: true
+ !fromLockscreenScene || !isFalseTouch
+ } ?: true
+
+ if (isAllowed) {
+ // A scene change is guaranteed; log it.
+ logger.logSceneChanged(
+ from = fromScene,
+ to = toScene,
+ reason = "user interaction",
+ isInstant = false,
+ )
+ }
+ return isAllowed
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 50ea3bb..448f7c4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -99,6 +99,9 @@
) {
val displays = getDisplaysToScreenshot(screenshotRequest.type)
val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
+ if (displays.isEmpty()) {
+ Log.wtf(TAG, "No displays found for screenshot.")
+ }
displays.forEach { display ->
val displayId = display.displayId
var screenshotHandler: ScreenshotHandler =
@@ -219,8 +222,7 @@
}
private fun getScreenshotController(display: Display): InteractiveScreenshotHandler {
- val controller =
- screenshotController ?: interactiveScreenshotHandlerFactory.create(display)
+ val controller = screenshotController ?: interactiveScreenshotHandlerFactory.create(display)
screenshotController = controller
return controller
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
index 3fe3162..29450a2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
@@ -26,7 +26,7 @@
import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult
import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
-import com.android.wm.shell.shared.desktopmode.DesktopModeFlags
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import javax.inject.Inject
import kotlinx.coroutines.flow.first
@@ -48,7 +48,7 @@
return NotMatched(policy = NAME, reason = SHADE_EXPANDED)
}
- if (DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context)) {
+ if (DesktopModeStatus.canEnterDesktopMode(context)) {
content.rootTasks.firstOrNull()?.also {
if (it.windowingMode == WINDOWING_MODE_FREEFORM) {
return NotMatched(policy = NAME, reason = DESKTOP_MODE_ENABLED)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
index ee1944e..ad27da9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
@@ -214,8 +214,7 @@
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
- if (mCurrentDraggingBoundary != CropBoundary.NONE
- && mActivePointerId == event.getPointerId(mActivePointerId)) {
+ if (mCurrentDraggingBoundary != CropBoundary.NONE) {
updateListener(MotionEvent.ACTION_UP, event.getX(0));
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
index 693cc4a..2b9daef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
@@ -28,6 +28,9 @@
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
+import static com.android.systemui.Flags.enableViewCaptureTracing;
+import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
+
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
@@ -73,12 +76,16 @@
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.CoreStartable;
import com.android.systemui.res.R;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.util.settings.SecureSettings;
+import kotlin.Lazy;
+
import javax.inject.Inject;
/**
@@ -105,12 +112,12 @@
private final CommandQueue mCommandQueue;
private ClingWindowView mClingWindow;
- /** The last {@link WindowManager} that is used to add the confirmation window. */
+ /** The wrapper on the last {@link WindowManager} used to add the confirmation window. */
@Nullable
- private WindowManager mWindowManager;
+ private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
/**
- * The WindowContext that is registered with {@link #mWindowManager} with options to specify the
- * {@link RootDisplayArea} to attach the confirmation window.
+ * The WindowContext that is registered with {@link #mViewCaptureAwareWindowManager} with
+ * options to specify the {@link RootDisplayArea} to attach the confirmation window.
*/
@Nullable
private Context mWindowContext;
@@ -127,15 +134,19 @@
private ContentObserver mContentObserver;
+ private Lazy<ViewCapture> mLazyViewCapture;
+
@Inject
public ImmersiveModeConfirmation(Context context, CommandQueue commandQueue,
- SecureSettings secureSettings) {
+ SecureSettings secureSettings,
+ dagger.Lazy<ViewCapture> daggerLazyViewCapture) {
mSysUiContext = context;
final Display display = mSysUiContext.getDisplay();
mDisplayContext = display.getDisplayId() == DEFAULT_DISPLAY
? mSysUiContext : mSysUiContext.createDisplayContext(display);
mCommandQueue = commandQueue;
mSecureSettings = secureSettings;
+ mLazyViewCapture = toKotlinLazy(daggerLazyViewCapture);
}
boolean loadSetting(int currentUserId) {
@@ -239,14 +250,14 @@
private void handleHide() {
if (mClingWindow != null) {
if (DEBUG) Log.d(TAG, "Hiding immersive mode confirmation");
- if (mWindowManager != null) {
+ if (mViewCaptureAwareWindowManager != null) {
try {
- mWindowManager.removeView(mClingWindow);
+ mViewCaptureAwareWindowManager.removeView(mClingWindow);
} catch (WindowManager.InvalidDisplayException e) {
Log.w(TAG, "Fail to hide the immersive confirmation window because of "
+ e);
}
- mWindowManager = null;
+ mViewCaptureAwareWindowManager = null;
mWindowContext = null;
}
mClingWindow = null;
@@ -505,8 +516,8 @@
* confirmation window.
*/
@NonNull
- private WindowManager createWindowManager(int rootDisplayAreaId) {
- if (mWindowManager != null) {
+ private ViewCaptureAwareWindowManager createWindowManager(int rootDisplayAreaId) {
+ if (mViewCaptureAwareWindowManager != null) {
throw new IllegalStateException(
"Must not create a new WindowManager while there is an existing one");
}
@@ -515,8 +526,10 @@
mWindowContextRootDisplayAreaId = rootDisplayAreaId;
mWindowContext = mDisplayContext.createWindowContext(
IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, options);
- mWindowManager = mWindowContext.getSystemService(WindowManager.class);
- return mWindowManager;
+ WindowManager wm = mWindowContext.getSystemService(WindowManager.class);
+ mViewCaptureAwareWindowManager = new ViewCaptureAwareWindowManager(wm, mLazyViewCapture,
+ enableViewCaptureTracing());
+ return mViewCaptureAwareWindowManager;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 7c3072d..6a2c602 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -139,6 +139,9 @@
/** Fraction of shade expansion. */
private float mExpansionFraction;
+ /** Fraction of QS expansion. 0 when in shade, 1 when in QS. */
+ private float mQsExpansionFraction;
+
/** Height of the notifications panel when expansion completes. */
private float mStackEndHeight;
@@ -208,6 +211,14 @@
}
/**
+ * @param expansionFraction Fraction of QS expansion.
+ */
+ public void setQsExpansionFraction(float expansionFraction) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ mQsExpansionFraction = expansionFraction;
+ }
+
+ /**
* @param isSwipingUp Whether we are swiping up.
*/
public void setSwipingUp(boolean isSwipingUp) {
@@ -258,6 +269,14 @@
}
/**
+ * @return Fraction of QS expansion.
+ */
+ public float getQsExpansionFraction() {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f;
+ return mQsExpansionFraction;
+ }
+
+ /**
* @see #getStackHeight()
*/
public void setStackHeight(float stackHeight) {
@@ -837,6 +856,7 @@
pw.println("mAppearFraction=" + mAppearFraction);
pw.println("mAppearing=" + mAppearing);
pw.println("mExpansionFraction=" + mExpansionFraction);
+ pw.println("mQsExpansionFraction=" + mQsExpansionFraction);
pw.println("mExpandingVelocity=" + mExpandingVelocity);
pw.println("mOverScrollTopAmount=" + mOverScrollTopAmount);
pw.println("mOverScrollBottomAmount=" + mOverScrollBottomAmount);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 07b0b83..f26f840 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1244,6 +1244,7 @@
@Override
public void setHeadsUpTop(float headsUpTop) {
mAmbientState.setHeadsUpTop(headsUpTop);
+ requestChildrenUpdate();
}
@Override
@@ -1563,6 +1564,12 @@
}
}
+ @Override
+ public void setQsExpandFraction(float expandFraction) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ mAmbientState.setQsExpansionFraction(expandFraction);
+ }
+
/**
* Update the height of the panel.
*
@@ -2534,6 +2541,11 @@
return getTopHeadsUpIntrinsicHeight();
}
+ @Override
+ public int getHeadsUpInset() {
+ return mHeadsUpInset;
+ }
+
/**
* Calculate the gap height between two different views
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index f35d666..55f0566 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -132,6 +132,7 @@
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpNotificationViewControllerEmptyImpl;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.phone.HeadsUpTouchHelper.HeadsUpNotificationViewController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -773,7 +774,7 @@
mHeadsUpManager,
statusBarService.get(),
getHeadsUpCallback(),
- new HeadsUpNotificationViewControllerEmptyImpl()
+ getHeadsUpNotificationViewController()
);
}
mNotificationRoundnessManager = notificationRoundnessManager;
@@ -1851,6 +1852,32 @@
return mTouchHandler;
}
+ private HeadsUpNotificationViewController getHeadsUpNotificationViewController() {
+ HeadsUpNotificationViewController headsUpViewController;
+ if (SceneContainerFlag.isEnabled()) {
+ headsUpViewController = new HeadsUpNotificationViewController() {
+ @Override
+ public void setHeadsUpDraggingStartingHeight(int startHeight) {
+ // do nothing
+ }
+
+ @Override
+ public void setTrackedHeadsUp(ExpandableNotificationRow expandableNotificationRow) {
+ setTrackingHeadsUp(expandableNotificationRow);
+ }
+
+ @Override
+ public void startExpand(float newX, float newY, boolean startTracking,
+ float expandedHeight) {
+ // do nothing
+ }
+ };
+ } else {
+ headsUpViewController = new HeadsUpNotificationViewControllerEmptyImpl();
+ }
+ return headsUpViewController;
+ }
+
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("mMaxAlphaFromView=" + mMaxAlphaFromView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index aee1d3e..0c2b5ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.notification.stack;
-import static androidx.core.math.MathUtils.clamp;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -890,7 +888,14 @@
continue;
}
ExpandableViewState childState = row.getViewState();
- if (topHeadsUpEntry == null && row.mustStayOnScreen() && !childState.headsUpIsVisible) {
+ boolean shouldSetTopHeadsUpEntry;
+ if (SceneContainerFlag.isEnabled()) {
+ shouldSetTopHeadsUpEntry = row.isHeadsUp();
+ } else {
+ shouldSetTopHeadsUpEntry = row.mustStayOnScreen();
+ }
+ if (topHeadsUpEntry == null && shouldSetTopHeadsUpEntry
+ && !childState.headsUpIsVisible) {
topHeadsUpEntry = row;
childState.location = ExpandableViewState.LOCATION_FIRST_HUN;
}
@@ -898,7 +903,7 @@
float unmodifiedEndLocation = childState.getYTranslation() + childState.height;
if (mIsExpanded) {
if (SceneContainerFlag.isEnabled()) {
- if (shouldHunBeVisibleWhenScrolled(row.mustStayOnScreen(),
+ if (shouldHunBeVisibleWhenScrolled(row.isHeadsUp(),
childState.headsUpIsVisible, row.showingPulsing(),
ambientState.isOnKeyguard(), row.getEntry().isStickyAndNotDemoted())) {
// the height of this child before clamping it to the top
@@ -909,10 +914,19 @@
/* viewState = */ childState
);
float baseZ = ambientState.getBaseZHeight();
- if (headsUpTranslation < ambientState.getStackTop()) {
- // HUN displayed above the stack top, it needs a fix shadow
- childState.setZTranslation(baseZ + mPinnedZTranslationExtra);
- } else {
+ if (headsUpTranslation > ambientState.getStackTop()
+ && row.isAboveShelf()) {
+ // HUN displayed outside of the stack during transition from Gone/LS;
+ // add a shadow that corresponds to the transition progress.
+ float fraction = 1 - ambientState.getExpansionFraction();
+ childState.setZTranslation(baseZ + fraction * mPinnedZTranslationExtra);
+ } else if (headsUpTranslation < ambientState.getStackTop()
+ && row.isAboveShelf()) {
+ // HUN displayed outside of the stack during transition from QS;
+ // add a shadow that corresponds to the transition progress.
+ float fraction = ambientState.getQsExpansionFraction();
+ childState.setZTranslation(baseZ + fraction * mPinnedZTranslationExtra);
+ } else if (headsUpTranslation > ambientState.getStackTop()) {
// HUN displayed within the stack, add a shadow if it overlaps with
// other elements.
//
@@ -927,6 +941,8 @@
/* baseZ = */ baseZ,
/* viewState = */ childState
);
+ } else {
+ childState.setZTranslation(baseZ);
}
if (isTopEntry && row.isAboveShelf()) {
clampHunToMaxTranslation(
@@ -1081,7 +1097,7 @@
if (scrollingContentTopPadding > 0f) {
// scrollingContentTopPadding makes a gap between the bottom of the HUN and the top
// of the scrolling content. Use this to animate to the full shadow.
- shadowFraction = clamp(overlap / scrollingContentTopPadding, 0f, 1f);
+ shadowFraction = Math.clamp(overlap / scrollingContentTopPadding, 0f, 1f);
}
if (overlap > 0.0f) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index 6226fe7..1289cec 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,18 @@
/** 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)
+ /** Sets whether the view is displayed in pulsing mode. */
+ fun setPulsing(pulsing: Boolean, animated: Boolean)
+
+ /** Gets the inset for HUNs when they are not visible */
+ fun getHeadsUpInset(): Int
+
/** Adds a listener to be notified, when the stack height might have changed. */
fun addStackHeightChangedListener(runnable: Runnable)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 950b14d..c044f6f 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,19 @@
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.isPulsing.collect { isPulsing ->
+ view.setPulsing(isPulsing, viewModel.shouldAnimatePulse.value)
+ }
+ }
+ 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 f28ce6c..0e984cf 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
@@ -39,6 +39,8 @@
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
@@ -127,6 +129,9 @@
.distinctUntilChanged()
.dumpWhileCollecting("expandFraction")
+ val qsExpandFraction: Flow<Float> =
+ shadeInteractor.qsExpansion.dumpWhileCollecting("qsExpandFraction")
+
val shouldResetStackTop: Flow<Boolean> =
sceneInteractor.transitionState
.mapNotNull { state ->
@@ -213,6 +218,23 @@
}
}
+ /** Whether the notification stack is displayed in pulsing mode. */
+ val isPulsing: Flow<Boolean> by lazy {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+ flowOf(false)
+ } else {
+ keyguardInteractor.get().isPulsing.dumpWhileCollecting("isPulsing")
+ }
+ }
+
+ val shouldAnimatePulse: StateFlow<Boolean> by lazy {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+ MutableStateFlow(false)
+ } else {
+ keyguardInteractor.get().isAodAvailable
+ }
+ }
+
@AssistedFactory
interface Factory {
fun create(): NotificationScrollViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 0067316..f649418 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -25,6 +25,7 @@
import com.android.systemui.doze.DozeLog;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import javax.inject.Inject;
@@ -124,6 +125,11 @@
// Begin pulse. Note that it's very important that the pulse finished callback
// be invoked when we're done so that the caller can drop the pulse wakelock.
+ if (SceneContainerFlag.isEnabled()) {
+ // ScrimController.Callback#onDisplayBlanked is no longer triggered when flexiglass is
+ // on, but we still need to signal that pulsing has started.
+ callback.onPulseStarted();
+ }
mPulseCallback = callback;
mPulseReason = reason;
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index 1c8041f..a3b1867 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -16,7 +16,6 @@
package com.android.systemui.touchpad.tutorial.ui.composable
-import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.airbnb.lottie.compose.rememberLottieDynamicProperties
@@ -67,7 +66,6 @@
val onTertiaryFixed = LocalAndroidColorScheme.current.onTertiaryFixed
val onTertiaryFixedVariant = LocalAndroidColorScheme.current.onTertiaryFixedVariant
val tertiaryFixedDim = LocalAndroidColorScheme.current.tertiaryFixedDim
- val surfaceContainer = MaterialTheme.colorScheme.surfaceContainer
val dynamicProperties =
rememberLottieDynamicProperties(
rememberColorFilterProperty(".tertiaryFixedDim", tertiaryFixedDim),
@@ -76,10 +74,9 @@
rememberColorFilterProperty(".onTertiaryFixedVariant", onTertiaryFixedVariant)
)
val screenColors =
- remember(onTertiaryFixed, surfaceContainer, tertiaryFixedDim, dynamicProperties) {
+ remember(dynamicProperties) {
TutorialScreenConfig.Colors(
background = onTertiaryFixed,
- successBackground = surfaceContainer,
title = tertiaryFixedDim,
animationColors = dynamicProperties,
)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
index 0a6283a..d4eb0cd 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -16,7 +16,6 @@
package com.android.systemui.touchpad.tutorial.ui.composable
-import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.airbnb.lottie.compose.rememberLottieDynamicProperties
@@ -66,7 +65,6 @@
val primaryFixedDim = LocalAndroidColorScheme.current.primaryFixedDim
val onPrimaryFixed = LocalAndroidColorScheme.current.onPrimaryFixed
val onPrimaryFixedVariant = LocalAndroidColorScheme.current.onPrimaryFixedVariant
- val surfaceContainer = MaterialTheme.colorScheme.surfaceContainer
val dynamicProperties =
rememberLottieDynamicProperties(
rememberColorFilterProperty(".primaryFixedDim", primaryFixedDim),
@@ -74,10 +72,9 @@
rememberColorFilterProperty(".onPrimaryFixedVariant", onPrimaryFixedVariant)
)
val screenColors =
- remember(surfaceContainer, dynamicProperties) {
+ remember(dynamicProperties) {
TutorialScreenConfig.Colors(
background = onPrimaryFixed,
- successBackground = surfaceContainer,
title = primaryFixedDim,
animationColors = dynamicProperties,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index c425e82..5fc1971 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -18,6 +18,7 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static com.android.systemui.Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED;
@@ -26,7 +27,6 @@
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_MINIMIZED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
-import static com.android.systemui.flags.Flags.CLIPBOARD_SHARED_TRANSITIONS;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -47,6 +47,8 @@
import android.graphics.Rect;
import android.net.Uri;
import android.os.PersistableBundle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.view.WindowInsets;
import android.view.textclassifier.TextLinks;
@@ -130,7 +132,6 @@
new ClipData.Item("Test Item"));
mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, true); // turned off for legacy tests
- mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, true); // turned off for old tests
}
/**
@@ -299,8 +300,8 @@
}
@Test
+ @DisableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
public void test_viewCallbacks_onShareTapped_sharedTransitionsOff() {
- mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
initController();
mOverlayController.setClipData(mSampleClipData, "");
@@ -311,6 +312,7 @@
}
@Test
+ @EnableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
public void test_viewCallbacks_onShareTapped() {
initController();
mOverlayController.setClipData(mSampleClipData, "");
@@ -324,8 +326,8 @@
}
@Test
+ @DisableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
public void test_viewCallbacks_onDismissTapped_sharedTransitionsOff() {
- mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
initController();
mOverlayController.setClipData(mSampleClipData, "");
@@ -336,6 +338,7 @@
}
@Test
+ @EnableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
public void test_viewCallbacks_onDismissTapped() {
initController();
@@ -350,7 +353,6 @@
@Test
public void test_multipleDismissals_dismissesOnce_sharedTransitionsOff() {
- mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
initController();
mCallbacks.onSwipeDismissInitiated(mAnimator);
mCallbacks.onDismissButtonTapped();
@@ -362,6 +364,7 @@
}
@Test
+ @EnableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
public void test_multipleDismissals_dismissesOnce() {
initController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
index a5fbfb5..be9fcc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
@@ -18,11 +18,13 @@
import android.content.ComponentName
import android.content.Context
+import android.content.res.Resources
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.internal.R
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.screenshot.data.model.DisplayContentModel
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
@@ -57,6 +59,7 @@
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class WorkProfilePolicyTest {
@@ -66,12 +69,17 @@
@JvmField @Rule(order = 2) val mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock lateinit var mContext: Context
+ @Mock lateinit var mResources: Resources
private val kosmos = Kosmos()
private lateinit var policy: WorkProfilePolicy
@Before
fun setUp() {
+ // Set desktop mode supported
+ whenever(mContext.resources).thenReturn(mResources)
+ whenever(mResources.getBoolean(R.bool.config_isDesktopModeSupported)).thenReturn(true)
+
policy = WorkProfilePolicy(kosmos.profileTypeRepository, mContext)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index ad029d7..7e79019 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -296,6 +296,7 @@
collapsedHeight = 100,
intrinsicHeight = intrinsicHunHeight,
)
+ ambientState.qsExpansionFraction = 1.0f
whenever(notificationRow.isAboveShelf).thenReturn(true)
// When
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index edb6390..3224b27 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -34,6 +34,7 @@
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
+import static com.android.server.pm.UserManagerService.enforceCurrentUserIfVisibleBackgroundEnabled;
import static com.android.window.flags.Flags.deleteCaptureDisplay;
import android.accessibilityservice.AccessibilityGestureEvent;
@@ -1100,11 +1101,14 @@
if (svcConnTracingEnabled()) {
logTraceSvcConn("performGlobalAction", "action=" + action);
}
+ int currentUserId;
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
return false;
}
+ currentUserId = mSystemSupport.getCurrentUserIdLocked();
}
+ enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
final long identity = Binder.clearCallingIdentity();
try {
return mSystemActionPerformer.performSystemAction(action);
@@ -2750,6 +2754,11 @@
@RequiresNoPermission
@Override
public void setAnimationScale(float scale) {
+ int currentUserId;
+ synchronized (mLock) {
+ currentUserId = mSystemSupport.getCurrentUserIdLocked();
+ }
+ enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
final long identity = Binder.clearCallingIdentity();
try {
Settings.Global.putFloat(
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 0aa750e..7cbb97e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -63,6 +63,7 @@
import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;
+import static com.android.server.pm.UserManagerService.enforceCurrentUserIfVisibleBackgroundEnabled;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.accessibilityservice.AccessibilityGestureEvent;
@@ -309,6 +310,8 @@
private final PowerManager mPowerManager;
+ private final UserManager mUserManager;
+
private final WindowManagerInternal mWindowManagerService;
private final AccessibilitySecurityPolicy mSecurityPolicy;
@@ -507,6 +510,7 @@
super(permissionEnforcer);
mContext = context;
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mUserManager = mContext.getSystemService(UserManager.class);
mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
mTraceManager = AccessibilityTraceManager.getInstance(
mWindowManagerService.getAccessibilityController(), this, mLock);
@@ -542,6 +546,7 @@
super(PermissionEnforcer.fromContext(context));
mContext = context;
mPowerManager = context.getSystemService(PowerManager.class);
+ mUserManager = context.getSystemService(UserManager.class);
mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
mTraceManager = AccessibilityTraceManager.getInstance(
mWindowManagerService.getAccessibilityController(), this, mLock);
@@ -1263,6 +1268,11 @@
@EnforcePermission(MANAGE_ACCESSIBILITY)
public void registerSystemAction(RemoteAction action, int actionId) {
registerSystemAction_enforcePermission();
+ int currentUserId;
+ synchronized (mLock) {
+ currentUserId = mCurrentUserId;
+ }
+ enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".registerSystemAction",
FLAGS_ACCESSIBILITY_MANAGER, "action=" + action + ";actionId=" + actionId);
@@ -1279,6 +1289,11 @@
@EnforcePermission(MANAGE_ACCESSIBILITY)
public void unregisterSystemAction(int actionId) {
unregisterSystemAction_enforcePermission();
+ int currentUserId;
+ synchronized (mLock) {
+ currentUserId = mCurrentUserId;
+ }
+ enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".unregisterSystemAction",
FLAGS_ACCESSIBILITY_MANAGER, "actionId=" + actionId);
@@ -1606,6 +1621,11 @@
@EnforcePermission(STATUS_BAR_SERVICE)
public void notifyAccessibilityButtonClicked(int displayId, String targetName) {
notifyAccessibilityButtonClicked_enforcePermission();
+ int currentUserId;
+ synchronized (mLock) {
+ currentUserId = mCurrentUserId;
+ }
+ enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".notifyAccessibilityButtonClicked",
FLAGS_ACCESSIBILITY_MANAGER,
@@ -1634,6 +1654,11 @@
@EnforcePermission(STATUS_BAR_SERVICE)
public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
notifyAccessibilityButtonVisibilityChanged_enforcePermission();
+ int currentUserId;
+ synchronized (mLock) {
+ currentUserId = mCurrentUserId;
+ }
+ enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".notifyAccessibilityButtonVisibilityChanged",
FLAGS_ACCESSIBILITY_MANAGER, "shown=" + shown);
@@ -1974,9 +1999,8 @@
this, 0, oldUserState.mUserId));
}
- // Announce user changes only if more that one exist.
- UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- final boolean announceNewUser = userManager.getUsers().size() > 1;
+ // Announce user changes only if more than one exist.
+ final boolean announceNewUser = mUserManager.getUsers().size() > 1;
// The user changed.
mCurrentUserId = userId;
@@ -2017,10 +2041,8 @@
synchronized (mLock) {
AccessibilityUserState userState = getCurrentUserStateLocked();
if (userState.isHandlingAccessibilityEventsLocked()) {
- UserManager userManager = (UserManager) mContext.getSystemService(
- Context.USER_SERVICE);
String message = mContext.getString(R.string.user_switched,
- userManager.getUserInfo(mCurrentUserId).name);
+ mUserManager.getUserInfo(mCurrentUserId).name);
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_ANNOUNCEMENT);
event.getText().add(message);
@@ -3185,6 +3207,7 @@
}
}
+ @GuardedBy("mLock")
private void updateWindowsForAccessibilityCallbackLocked(AccessibilityUserState userState) {
// We observe windows for accessibility only if there is at least
// one bound service that can retrieve window content that specified
@@ -3211,6 +3234,14 @@
for (int i = 0; i < displays.size(); i++) {
final Display display = displays.get(i);
if (display != null) {
+ // When supporting visible background users, only track windows on the display
+ // assigned to the current user. The proxy displays are registered only to the
+ // current user.
+ if (UserManager.isVisibleBackgroundUsersEnabled()
+ && !mProxyManager.isProxyedDisplay(display.getDisplayId())
+ && !mUmi.isUserVisible(mCurrentUserId, display.getDisplayId())) {
+ continue;
+ }
if (observingWindows) {
mA11yWindowManager.startTrackingWindows(display.getDisplayId(),
mProxyManager.isProxyedDisplay(display.getDisplayId()));
@@ -4799,6 +4830,11 @@
throws RemoteException {
registerProxyForDisplay_enforcePermission();
mSecurityPolicy.checkForAccessibilityPermissionOrRole();
+ int currentUserId;
+ synchronized (mLock) {
+ currentUserId = mCurrentUserId;
+ }
+ enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
if (client == null) {
return false;
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 9c6bcdf..53885fc 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -140,7 +140,7 @@
bindAppFunctionServiceUnchecked(requestInternal, serviceIntent, targetUser,
safeExecuteAppFunctionCallback,
/*bindFlags=*/ Context.BIND_AUTO_CREATE,
- /*timeoutInMillis=*/ mServiceConfig.getExecutionTimeoutConfig());
+ /*timeoutInMillis=*/ mServiceConfig.getExecuteAppFunctionTimeoutMillis());
}
private void bindAppFunctionServiceUnchecked(
diff --git a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java
index 35c6c78..4bc6e70 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java
@@ -26,5 +26,5 @@
/**
* Returns the maximum time to wait for an app function execution to be complete.
*/
- long getExecutionTimeoutConfig();
+ long getExecuteAppFunctionTimeoutMillis();
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java
index b203ead..e090317 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java
@@ -22,16 +22,17 @@
* Implementation of {@link ServiceConfig}
*/
public class ServiceConfigImpl implements ServiceConfig {
- static final String DEVICE_CONFIG_PROPERTY_EXECUTION_TIMEOUT = "execution_timeout";
- static final long DEFAULT_EXECUTION_TIMEOUT_MS = 5000L;
+ static final String DEVICE_CONFIG_PROPERTY_EXECUTION_TIMEOUT =
+ "execute_app_function_timeout_millis";
+ static final long DEFAULT_EXECUTE_APP_FUNCTION_TIMEOUT_MS = 5000L;
@Override
- public long getExecutionTimeoutConfig() {
+ public long getExecuteAppFunctionTimeoutMillis() {
return DeviceConfig.getLong(
NAMESPACE_APP_FUNCTIONS,
DEVICE_CONFIG_PROPERTY_EXECUTION_TIMEOUT,
- DEFAULT_EXECUTION_TIMEOUT_MS
+ DEFAULT_EXECUTE_APP_FUNCTION_TIMEOUT_MS
);
}
}
diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
index 8abbe56..22eefb3 100644
--- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
+++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
@@ -73,6 +73,8 @@
/**
* Utility methods to read backup tar file.
+ * Exteranl depenency:
+ * <li> @android.provider.Settings.Secure.V_TO_U_RESTORE_ALLOWLIST
*/
public class TarBackupReader {
private static final int TAR_HEADER_OFFSET_TYPE_CHAR = 156;
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index b3a2da4..d56f17b 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -20,6 +20,7 @@
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
+import static android.companion.CompanionDeviceManager.RESULT_SECURITY_ERROR;
import static android.content.ComponentName.createRelative;
import static android.content.pm.PackageManager.FEATURE_WATCH;
@@ -40,6 +41,7 @@
import android.companion.AssociatedDevice;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
+import android.companion.Flags;
import android.companion.IAssociationRequestCallback;
import android.content.ComponentName;
import android.content.Context;
@@ -182,7 +184,11 @@
String errorMessage = "3p apps are not allowed to create associations on watch.";
Slog.e(TAG, errorMessage);
try {
- callback.onFailure(RESULT_INTERNAL_ERROR);
+ if (Flags.associationFailureCode()) {
+ callback.onFailure(RESULT_SECURITY_ERROR, errorMessage);
+ } else {
+ callback.onFailure(RESULT_INTERNAL_ERROR, errorMessage);
+ }
} catch (RemoteException e) {
// ignored
}
@@ -251,9 +257,12 @@
} catch (SecurityException e) {
// Since, at this point the caller is our own UI, we need to catch the exception on
// forward it back to the application via the callback.
- Slog.e(TAG, e.getMessage());
try {
- callback.onFailure(RESULT_INTERNAL_ERROR);
+ if (Flags.associationFailureCode()) {
+ callback.onFailure(RESULT_SECURITY_ERROR, e.getMessage());
+ } else {
+ callback.onFailure(RESULT_INTERNAL_ERROR, e.getMessage());
+ }
} catch (RemoteException ignore) {
}
return;
@@ -378,7 +387,7 @@
// Send the association back via the app's callback
if (callback != null) {
try {
- callback.onFailure(RESULT_INTERNAL_ERROR);
+ callback.onFailure(RESULT_INTERNAL_ERROR, "Association doesn't exist.");
} catch (RemoteException ignore) {
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 756a750..f1bdc05 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -50,7 +50,6 @@
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
-import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
@@ -101,15 +100,12 @@
import static android.os.Process.BLUETOOTH_UID;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.INVALID_UID;
-import static android.os.Process.NETWORK_STACK_UID;
-import static android.os.Process.NFC_UID;
import static android.os.Process.PHONE_UID;
import static android.os.Process.PROC_OUT_LONG;
import static android.os.Process.PROC_SPACE_TERM;
import static android.os.Process.ROOT_UID;
import static android.os.Process.SCHED_FIFO;
import static android.os.Process.SCHED_RESET_ON_FORK;
-import static android.os.Process.SE_UID;
import static android.os.Process.SHELL_UID;
import static android.os.Process.SIGNAL_USR1;
import static android.os.Process.SYSTEM_UID;
@@ -145,8 +141,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALLOWLISTS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
@@ -155,7 +149,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LRU;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
@@ -265,10 +258,7 @@
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
-import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetManagerInternal;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
import android.content.AttributionSource;
import android.content.AutofillOptions;
import android.content.BroadcastReceiver;
@@ -316,9 +306,6 @@
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
-import android.media.audiofx.AudioEffect;
-import android.net.ConnectivityManager;
-import android.net.Proxy;
import android.net.Uri;
import android.os.AppZygote;
import android.os.BatteryStats;
@@ -374,9 +361,7 @@
import android.server.ServerProtoEnums;
import android.system.Os;
import android.system.OsConstants;
-import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.text.style.SuggestionSpan;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
@@ -386,7 +371,6 @@
import android.util.Log;
import android.util.MathUtils;
import android.util.Pair;
-import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -440,7 +424,6 @@
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;
@@ -452,7 +435,6 @@
import com.android.server.SystemServiceManager;
import com.android.server.ThreadPriorityBooster;
import com.android.server.Watchdog;
-import com.android.server.am.ComponentAliasResolver.Resolution;
import com.android.server.am.LowMemDetector.MemFactor;
import com.android.server.appop.AppOpsService;
import com.android.server.compat.PlatformCompat;
@@ -463,14 +445,12 @@
import com.android.server.job.JobSchedulerInternal;
import com.android.server.net.NetworkManagementInternal;
import com.android.server.os.NativeTombstoneManager;
-import com.android.server.pm.Computer;
import com.android.server.pm.Installer;
import com.android.server.pm.SaferIntentUtils;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.SELinuxUtil;
-import com.android.server.pm.snapshot.PackageDataSnapshot;
import com.android.server.power.stats.BatteryStatsImpl;
import com.android.server.sdksandbox.SdkSandboxManagerLocal;
import com.android.server.stats.pull.StatsPullAtomService;
@@ -513,7 +493,6 @@
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@@ -538,7 +517,6 @@
static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM;
static final String TAG_BACKUP = TAG + POSTFIX_BACKUP;
- private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
private static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP;
private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
@@ -566,9 +544,6 @@
static final String SYSTEM_USER_HOME_NEEDED = "ro.system_user_home_needed";
- // Maximum number of receivers an app can register.
- private static final int MAX_RECEIVERS_ALLOWED_PER_APP = 1000;
-
// How long we wait for a launched process to attach to the activity manager
// before we decide it's never going to come up for real.
static final int PROC_START_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
@@ -653,15 +628,6 @@
static final String EXTRA_BUGREPORT_NONCE = "android.intent.extra.BUGREPORT_NONCE";
static final String EXTRA_EXTRA_ATTACHMENT_URI =
"android.intent.extra.EXTRA_ATTACHMENT_URI";
- /**
- * It is now required for apps to explicitly set either
- * {@link android.content.Context#RECEIVER_EXPORTED} or
- * {@link android.content.Context#RECEIVER_NOT_EXPORTED} when registering a receiver for an
- * unprotected broadcast in code.
- */
- @ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- private static final long DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED = 161145287L;
/**
* The maximum number of bytes that {@link #setProcessStateSummary} accepts.
@@ -738,11 +704,9 @@
// so that dispatch of foreground broadcasts gets precedence.
private BroadcastQueue mBroadcastQueue;
- @GuardedBy("this")
- BroadcastStats mLastBroadcastStats;
-
- @GuardedBy("this")
- BroadcastStats mCurBroadcastStats;
+ // TODO: Add a consistent way of accessing the methods within this class. Currently, some
+ // methods require access while holding a lock, while others do not.
+ BroadcastController mBroadcastController;
TraceErrorLogger mTraceErrorLogger;
@@ -862,12 +826,6 @@
};
/**
- * Broadcast actions that will always be deliverable to unlaunched/background apps
- */
- @GuardedBy("this")
- private ArraySet<String> mBackgroundLaunchBroadcasts;
-
- /**
* When an app has restrictions on the other apps that can have associations with it,
* it appears here with a set of the allowed apps and also track debuggability of the app.
*/
@@ -1135,97 +1093,6 @@
private final HashSet<Integer> mAlreadyLoggedViolatedStacks = new HashSet<Integer>();
private static final int MAX_DUP_SUPPRESSED_STACKS = 5000;
- /**
- * Keeps track of all IIntentReceivers that have been registered for broadcasts.
- * Hash keys are the receiver IBinder, hash value is a ReceiverList.
- */
- @GuardedBy("this")
- final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
-
- /**
- * Resolver for broadcast intents to registered receivers.
- * Holds BroadcastFilter (subclass of IntentFilter).
- */
- final IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver
- = new IntentResolver<BroadcastFilter, BroadcastFilter>() {
- @Override
- protected boolean allowFilterResult(
- BroadcastFilter filter, List<BroadcastFilter> dest) {
- IBinder target = filter.receiverList.receiver.asBinder();
- for (int i = dest.size() - 1; i >= 0; i--) {
- if (dest.get(i).receiverList.receiver.asBinder() == target) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- protected BroadcastFilter newResult(@NonNull Computer computer, BroadcastFilter filter,
- int match, int userId, long customFlags) {
- if (userId == UserHandle.USER_ALL || filter.owningUserId == UserHandle.USER_ALL
- || userId == filter.owningUserId) {
- return super.newResult(computer, filter, match, userId, customFlags);
- }
- return null;
- }
-
- @Override
- protected IntentFilter getIntentFilter(@NonNull BroadcastFilter input) {
- return input;
- }
-
- @Override
- protected BroadcastFilter[] newArray(int size) {
- return new BroadcastFilter[size];
- }
-
- @Override
- protected boolean isPackageForFilter(String packageName, BroadcastFilter filter) {
- return packageName.equals(filter.packageName);
- }
- };
-
- /**
- * State of all active sticky broadcasts per user. Keys are the action of the
- * sticky Intent, values are an ArrayList of all broadcasted intents with
- * that action (which should usually be one). The SparseArray is keyed
- * by the user ID the sticky is for, and can include UserHandle.USER_ALL
- * for stickies that are sent to all users.
- */
- @GuardedBy("mStickyBroadcasts")
- final SparseArray<ArrayMap<String, ArrayList<StickyBroadcast>>> mStickyBroadcasts =
- new SparseArray<>();
-
- @VisibleForTesting
- static final class StickyBroadcast {
- public Intent intent;
- public boolean deferUntilActive;
- public int originalCallingUid;
- /** The snapshot process state of the app who sent this broadcast */
- public int originalCallingAppProcessState;
- public String resolvedDataType;
-
- public static StickyBroadcast create(Intent intent, boolean deferUntilActive,
- int originalCallingUid, int originalCallingAppProcessState,
- String resolvedDataType) {
- final StickyBroadcast b = new StickyBroadcast();
- b.intent = intent;
- b.deferUntilActive = deferUntilActive;
- b.originalCallingUid = originalCallingUid;
- b.originalCallingAppProcessState = originalCallingAppProcessState;
- b.resolvedDataType = resolvedDataType;
- return b;
- }
-
- @Override
- public String toString() {
- return "{intent=" + intent + ", defer=" + deferUntilActive + ", originalCallingUid="
- + originalCallingUid + ", originalCallingAppProcessState="
- + originalCallingAppProcessState + ", type=" + resolvedDataType + "}";
- }
- }
-
final ActiveServices mServices;
final static class Association {
@@ -1687,7 +1554,7 @@
// Encapsulates the global setting "hidden_api_blacklist_exemptions"
final HiddenApiSettings mHiddenApiBlacklist;
- private final PlatformCompat mPlatformCompat;
+ final PlatformCompat mPlatformCompat;
PackageManagerInternal mPackageManagerInt;
PermissionManagerServiceInternal mPermissionManagerInt;
@@ -2328,7 +2195,7 @@
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();
@@ -2542,6 +2409,7 @@
mPendingStartActivityUids = new PendingStartActivityUids();
mUseFifoUiScheduling = false;
mBroadcastQueue = injector.getBroadcastQueue(this);
+ mBroadcastController = new BroadcastController(mContext, this, mBroadcastQueue);
mComponentAliasResolver = new ComponentAliasResolver(this);
}
@@ -2584,6 +2452,7 @@
: new OomAdjuster(this, mProcessList, activeUids);
mBroadcastQueue = mInjector.getBroadcastQueue(this);
+ mBroadcastController = new BroadcastController(mContext, this, mBroadcastQueue);
mServices = new ActiveServices(this);
mCpHelper = new ContentProviderHelper(this, true);
@@ -2652,6 +2521,7 @@
void setBroadcastQueueForTest(BroadcastQueue broadcastQueue) {
mBroadcastQueue = broadcastQueue;
+ mBroadcastController.setBroadcastQueueForTest(broadcastQueue);
}
BroadcastQueue getBroadcastQueue() {
@@ -2686,18 +2556,6 @@
mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
}
- private ArraySet<String> getBackgroundLaunchBroadcasts() {
- if (mBackgroundLaunchBroadcasts == null) {
- mBackgroundLaunchBroadcasts = SystemConfig.getInstance().getAllowImplicitBroadcasts();
- }
- return mBackgroundLaunchBroadcasts;
- }
-
- private String getWearRemoteIntentAction() {
- return mContext.getResources().getString(
- com.android.internal.R.string.config_wearRemoteIntentAction);
- }
-
/**
* Ensures that the given package name has an explicit set of allowed associations.
* If it does not, give it an empty set.
@@ -2768,7 +2626,7 @@
}
/** Updates allowed associations for app info (specifically, based on debuggability). */
- private void updateAssociationForApp(ApplicationInfo appInfo) {
+ void updateAssociationForApp(ApplicationInfo appInfo) {
ensureAllowedAssociations();
PackageAssociationInfo pai = mAllowedAssociations.get(appInfo.packageName);
if (pai != null) {
@@ -3894,7 +3752,7 @@
forceStopPackage(packageName, userId, ActivityManager.FLAG_OR_STOPPED, null);
}
- private void forceStopPackage(final String packageName, int userId, int userRunningFlags,
+ void forceStopPackage(final String packageName, int userId, int userRunningFlags,
String reason) {
if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
!= PackageManager.PERMISSION_GRANTED) {
@@ -4222,7 +4080,7 @@
mPackageManagerInt.sendPackageRestartedBroadcast(packageName, uid, flags);
}
- private void cleanupDisabledPackageComponentsLocked(
+ void cleanupDisabledPackageComponentsLocked(
String packageName, int userId, String[] changedClasses) {
Set<String> disabledClasses = null;
@@ -4460,9 +4318,7 @@
if (packageName == null) {
// Remove all sticky broadcasts from this user.
- synchronized (mStickyBroadcasts) {
- mStickyBroadcasts.remove(userId);
- }
+ mBroadcastController.removeStickyBroadcasts(userId);
}
ArrayList<ContentProviderRecord> providers = new ArrayList<>();
@@ -9335,10 +9191,6 @@
Settings.Global.DEVICE_PROVISIONED, 0) != 0;
}
- private void startBroadcastObservers() {
- mBroadcastQueue.start(mContext.getContentResolver());
- }
-
private void updateForceBackgroundCheck(boolean enabled) {
synchronized (this) {
synchronized (mProcLock) {
@@ -10530,14 +10382,15 @@
pw.println(
"-------------------------------------------------------------------------------");
}
- dumpBroadcastsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+ mBroadcastController.dumpBroadcastsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
pw.println(
"-------------------------------------------------------------------------------");
}
if (dumpAll || dumpPackage != null) {
- dumpBroadcastStatsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+ mBroadcastController.dumpBroadcastStatsLocked(fd, pw, args, opti, dumpAll,
+ dumpPackage);
pw.println();
if (dumpAll) {
pw.println(
@@ -10788,7 +10641,7 @@
} else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
// output proto is ActivityManagerServiceDumpBroadcastsProto
synchronized (this) {
- writeBroadcastsToProtoLocked(proto);
+ mBroadcastController.writeBroadcastsToProtoLocked(proto);
}
} else if ("provider".equals(cmd)) {
String[] newArgs;
@@ -10852,7 +10705,7 @@
proto.end(activityToken);
long broadcastToken = proto.start(ActivityManagerServiceProto.BROADCASTS);
- writeBroadcastsToProtoLocked(proto);
+ mBroadcastController.writeBroadcastsToProtoLocked(proto);
proto.end(broadcastToken);
long serviceToken = proto.start(ActivityManagerServiceProto.SERVICES);
@@ -10912,7 +10765,8 @@
opti++;
}
synchronized (this) {
- dumpBroadcastsLocked(fd, pw, args, opti, /* dumpAll= */ true, dumpPackage);
+ mBroadcastController.dumpBroadcastsLocked(fd, pw, args, opti,
+ /* dumpAll= */ true, dumpPackage);
}
} else if ("broadcast-stats".equals(cmd)) {
if (opti < args.length) {
@@ -10921,10 +10775,11 @@
}
synchronized (this) {
if (dumpCheckinFormat) {
- dumpBroadcastStatsCheckinLocked(fd, pw, args, opti, dumpCheckin,
- dumpPackage);
+ mBroadcastController.dumpBroadcastStatsCheckinLocked(fd, pw, args, opti,
+ dumpCheckin, dumpPackage);
} else {
- dumpBroadcastStatsLocked(fd, pw, args, opti, true, dumpPackage);
+ mBroadcastController.dumpBroadcastStatsLocked(fd, pw, args, opti, true,
+ dumpPackage);
}
}
} else if ("intents".equals(cmd) || "i".equals(cmd)) {
@@ -11078,7 +10933,8 @@
// No piece of data specified, dump everything.
if (dumpCheckinFormat) {
- dumpBroadcastStatsCheckinLocked(fd, pw, args, opti, dumpCheckin, dumpPackage);
+ mBroadcastController.dumpBroadcastStatsCheckinLocked(fd, pw, args, opti, dumpCheckin,
+ dumpPackage);
} else {
if (dumpClient) {
// dumpEverything() will take the lock when needed, and momentarily drop
@@ -11789,42 +11645,6 @@
}
}
- void writeBroadcastsToProtoLocked(ProtoOutputStream proto) {
- if (mRegisteredReceivers.size() > 0) {
- Iterator it = mRegisteredReceivers.values().iterator();
- while (it.hasNext()) {
- ReceiverList r = (ReceiverList)it.next();
- r.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_LIST);
- }
- }
- mReceiverResolver.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_RESOLVER);
- mBroadcastQueue.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
- synchronized (mStickyBroadcasts) {
- for (int user = 0; user < mStickyBroadcasts.size(); user++) {
- long token = proto.start(
- ActivityManagerServiceDumpBroadcastsProto.STICKY_BROADCASTS);
- proto.write(StickyBroadcastProto.USER, mStickyBroadcasts.keyAt(user));
- for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
- : mStickyBroadcasts.valueAt(user).entrySet()) {
- long actionToken = proto.start(StickyBroadcastProto.ACTIONS);
- proto.write(StickyBroadcastProto.StickyAction.NAME, ent.getKey());
- for (StickyBroadcast broadcast : ent.getValue()) {
- broadcast.intent.dumpDebug(proto, StickyBroadcastProto.StickyAction.INTENTS,
- false, true, true, false);
- }
- proto.end(actionToken);
- }
- proto.end(token);
- }
- }
-
- long handlerToken = proto.start(ActivityManagerServiceDumpBroadcastsProto.HANDLER);
- proto.write(ActivityManagerServiceDumpBroadcastsProto.MainHandler.HANDLER, mHandler.toString());
- mHandler.getLooper().dumpDebug(proto,
- ActivityManagerServiceDumpBroadcastsProto.MainHandler.LOOPER);
- proto.end(handlerToken);
- }
-
void dumpAllowedAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage) {
pw.println(
@@ -11860,219 +11680,6 @@
}
}
- @NeverCompile
- void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- int opti, boolean dumpAll, String dumpPackage) {
- boolean dumpConstants = true;
- boolean dumpHistory = true;
- boolean needSep = false;
- boolean onlyHistory = false;
- boolean printedAnything = false;
- boolean onlyReceivers = false;
- int filteredUid = Process.INVALID_UID;
-
- if ("history".equals(dumpPackage)) {
- if (opti < args.length && "-s".equals(args[opti])) {
- dumpAll = false;
- }
- onlyHistory = true;
- dumpPackage = null;
- }
- if ("receivers".equals(dumpPackage)) {
- onlyReceivers = true;
- dumpPackage = null;
- if (opti + 2 <= args.length) {
- for (int i = opti; i < args.length; i++) {
- String arg = args[i];
- switch (arg) {
- case "--uid":
- filteredUid = getIntArg(pw, args, ++i, Process.INVALID_UID);
- if (filteredUid == Process.INVALID_UID) {
- return;
- }
- break;
- default:
- pw.printf("Invalid argument at index %d: %s\n", i, arg);
- return;
- }
- }
- }
- }
- if (DEBUG_BROADCAST) {
- Slogf.d(TAG_BROADCAST, "dumpBroadcastsLocked(): dumpPackage=%s, onlyHistory=%b, "
- + "onlyReceivers=%b, filteredUid=%d", dumpPackage, onlyHistory, onlyReceivers,
- filteredUid);
- }
-
- pw.println("ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts)");
- if (!onlyHistory && dumpAll) {
- if (mRegisteredReceivers.size() > 0) {
- boolean printed = false;
- Iterator it = mRegisteredReceivers.values().iterator();
- while (it.hasNext()) {
- ReceiverList r = (ReceiverList)it.next();
- if (dumpPackage != null && (r.app == null ||
- !dumpPackage.equals(r.app.info.packageName))) {
- continue;
- }
- if (filteredUid != Process.INVALID_UID && filteredUid != r.app.uid) {
- if (DEBUG_BROADCAST) {
- Slogf.v(TAG_BROADCAST, "dumpBroadcastsLocked(): skipping receiver whose"
- + " uid (%d) is not %d: %s", r.app.uid, filteredUid, r.app);
- }
- continue;
- }
- if (!printed) {
- pw.println(" Registered Receivers:");
- needSep = true;
- printed = true;
- printedAnything = true;
- }
- pw.print(" * "); pw.println(r);
- r.dump(pw, " ");
- }
- } else {
- if (onlyReceivers) {
- pw.println(" (no registered receivers)");
- }
- }
-
- if (!onlyReceivers) {
- if (mReceiverResolver.dump(pw, needSep
- ? "\n Receiver Resolver Table:" : " Receiver Resolver Table:",
- " ", dumpPackage, false, false)) {
- needSep = true;
- printedAnything = true;
- }
- }
- }
-
- if (!onlyReceivers) {
- needSep = mBroadcastQueue.dumpLocked(fd, pw, args, opti,
- dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
- printedAnything |= needSep;
- }
-
- needSep = true;
-
- synchronized (mStickyBroadcasts) {
- if (!onlyHistory && !onlyReceivers && mStickyBroadcasts != null
- && dumpPackage == null) {
- for (int user = 0; user < mStickyBroadcasts.size(); user++) {
- if (needSep) {
- pw.println();
- }
- needSep = true;
- printedAnything = true;
- pw.print(" Sticky broadcasts for user ");
- pw.print(mStickyBroadcasts.keyAt(user));
- pw.println(":");
- StringBuilder sb = new StringBuilder(128);
- for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
- : mStickyBroadcasts.valueAt(user).entrySet()) {
- pw.print(" * Sticky action ");
- pw.print(ent.getKey());
- if (dumpAll) {
- pw.println(":");
- ArrayList<StickyBroadcast> broadcasts = ent.getValue();
- final int N = broadcasts.size();
- for (int i = 0; i < N; i++) {
- final Intent intent = broadcasts.get(i).intent;
- final boolean deferUntilActive = broadcasts.get(i).deferUntilActive;
- sb.setLength(0);
- sb.append(" Intent: ");
- intent.toShortString(sb, false, true, false, false);
- pw.print(sb);
- if (deferUntilActive) {
- pw.print(" [D]");
- }
- pw.println();
- pw.print(" originalCallingUid: ");
- pw.println(broadcasts.get(i).originalCallingUid);
- pw.println();
- Bundle bundle = intent.getExtras();
- if (bundle != null) {
- pw.print(" extras: ");
- pw.println(bundle);
- }
- }
- } else {
- pw.println("");
- }
- }
- }
- }
- }
-
- if (!onlyHistory && !onlyReceivers && dumpAll) {
- pw.println();
- pw.println(" Queue " + mBroadcastQueue.toString() + ": "
- + mBroadcastQueue.describeStateLocked());
- pw.println(" mHandler:");
- mHandler.dump(new PrintWriterPrinter(pw), " ");
- needSep = true;
- printedAnything = true;
- }
-
- if (!printedAnything) {
- pw.println(" (nothing)");
- }
- }
-
- @NeverCompile
- void dumpBroadcastStatsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- int opti, boolean dumpAll, String dumpPackage) {
- if (mCurBroadcastStats == null) {
- return;
- }
-
- pw.println("ACTIVITY MANAGER BROADCAST STATS STATE (dumpsys activity broadcast-stats)");
- final long now = SystemClock.elapsedRealtime();
- if (mLastBroadcastStats != null) {
- pw.print(" Last stats (from ");
- TimeUtils.formatDuration(mLastBroadcastStats.mStartRealtime, now, pw);
- pw.print(" to ");
- TimeUtils.formatDuration(mLastBroadcastStats.mEndRealtime, now, pw);
- pw.print(", ");
- TimeUtils.formatDuration(mLastBroadcastStats.mEndUptime
- - mLastBroadcastStats.mStartUptime, pw);
- pw.println(" uptime):");
- if (!mLastBroadcastStats.dumpStats(pw, " ", dumpPackage)) {
- pw.println(" (nothing)");
- }
- pw.println();
- }
- pw.print(" Current stats (from ");
- TimeUtils.formatDuration(mCurBroadcastStats.mStartRealtime, now, pw);
- pw.print(" to now, ");
- TimeUtils.formatDuration(SystemClock.uptimeMillis()
- - mCurBroadcastStats.mStartUptime, pw);
- pw.println(" uptime):");
- if (!mCurBroadcastStats.dumpStats(pw, " ", dumpPackage)) {
- pw.println(" (nothing)");
- }
- }
-
- @NeverCompile
- void dumpBroadcastStatsCheckinLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- int opti, boolean fullCheckin, String dumpPackage) {
- if (mCurBroadcastStats == null) {
- return;
- }
-
- if (mLastBroadcastStats != null) {
- mLastBroadcastStats.dumpCheckinStats(pw, dumpPackage);
- if (fullCheckin) {
- mLastBroadcastStats = null;
- return;
- }
- }
- mCurBroadcastStats.dumpCheckinStats(pw, dumpPackage);
- if (fullCheckin) {
- mCurBroadcastStats = null;
- }
- }
-
void dumpPermissions(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage) {
@@ -14594,33 +14201,6 @@
// BROADCASTS
// =========================================================
- private boolean isInstantApp(ProcessRecord record, @Nullable String callerPackage, int uid) {
- if (UserHandle.getAppId(uid) < FIRST_APPLICATION_UID) {
- return false;
- }
- // Easy case -- we have the app's ProcessRecord.
- if (record != null) {
- return record.info.isInstantApp();
- }
- // Otherwise check with PackageManager.
- IPackageManager pm = AppGlobals.getPackageManager();
- try {
- if (callerPackage == null) {
- final String[] packageNames = pm.getPackagesForUid(uid);
- if (packageNames == null || packageNames.length == 0) {
- throw new IllegalArgumentException("Unable to determine caller package name");
- }
- // Instant Apps can't use shared uids, so its safe to only check the first package.
- callerPackage = packageNames[0];
- }
- mAppOpsService.checkPackage(uid, callerPackage);
- return pm.isInstantApp(callerPackage, UserHandle.getUserId(uid));
- } catch (RemoteException e) {
- Slog.e(TAG, "Error looking up if " + callerPackage + " is an instant app.", e);
- return true;
- }
- }
-
/**
* @deprecated Use {@link #registerReceiverWithFeature}
*/
@@ -14635,657 +14215,12 @@
public Intent registerReceiverWithFeature(IApplicationThread caller, String callerPackage,
String callerFeatureId, String receiverId, IIntentReceiver receiver,
IntentFilter filter, String permission, int userId, int flags) {
- traceRegistrationBegin(receiverId, receiver, filter, userId);
- try {
- return registerReceiverWithFeatureTraced(caller, callerPackage, callerFeatureId,
- receiverId, receiver, filter, permission, userId, flags);
- } finally {
- traceRegistrationEnd();
- }
- }
-
- private static void traceRegistrationBegin(String receiverId, IIntentReceiver receiver,
- IntentFilter filter, int userId) {
- if (!Flags.traceReceiverRegistration()) {
- return;
- }
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- final StringBuilder sb = new StringBuilder("registerReceiver: ");
- sb.append(Binder.getCallingUid()); sb.append('/');
- sb.append(receiverId == null ? "null" : receiverId); sb.append('/');
- final int actionsCount = filter.safeCountActions();
- if (actionsCount > 0) {
- for (int i = 0; i < actionsCount; ++i) {
- sb.append(filter.getAction(i));
- if (i != actionsCount - 1) sb.append(',');
- }
- } else {
- sb.append("null");
- }
- sb.append('/');
- sb.append('u'); sb.append(userId); sb.append('/');
- sb.append(receiver == null ? "null" : receiver.asBinder());
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, sb.toString());
- }
- }
-
- private static void traceRegistrationEnd() {
- if (!Flags.traceReceiverRegistration()) {
- return;
- }
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- }
- }
-
- private Intent registerReceiverWithFeatureTraced(IApplicationThread caller,
- String callerPackage, String callerFeatureId, String receiverId,
- IIntentReceiver receiver, IntentFilter filter, String permission,
- int userId, int flags) {
- enforceNotIsolatedCaller("registerReceiver");
- ArrayList<StickyBroadcast> stickyBroadcasts = null;
- ProcessRecord callerApp = null;
- final boolean visibleToInstantApps
- = (flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
-
- int callingUid;
- int callingPid;
- boolean instantApp;
- synchronized (mProcLock) {
- callerApp = getRecordForAppLOSP(caller);
- if (callerApp == null) {
- Slog.w(TAG, "registerReceiverWithFeature: no app for " + caller);
- return null;
- }
- if (callerApp.info.uid != SYSTEM_UID
- && !callerApp.getPkgList().containsKey(callerPackage)
- && !"android".equals(callerPackage)) {
- throw new SecurityException("Given caller package " + callerPackage
- + " is not running in process " + callerApp);
- }
- callingUid = callerApp.info.uid;
- callingPid = callerApp.getPid();
-
- instantApp = isInstantApp(callerApp, callerPackage, callingUid);
- }
- userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
- ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
-
- // Warn if system internals are registering for important broadcasts
- // without also using a priority to ensure they process the event
- // before normal apps hear about it
- if (UserHandle.isCore(callingUid)) {
- final int priority = filter.getPriority();
- final boolean systemPriority = (priority >= IntentFilter.SYSTEM_HIGH_PRIORITY)
- || (priority <= IntentFilter.SYSTEM_LOW_PRIORITY);
- if (!systemPriority) {
- final int N = filter.countActions();
- for (int i = 0; i < N; i++) {
- // TODO: expand to additional important broadcasts over time
- final String action = filter.getAction(i);
- if (action.startsWith("android.intent.action.USER_")
- || action.startsWith("android.intent.action.PACKAGE_")
- || action.startsWith("android.intent.action.UID_")
- || action.startsWith("android.intent.action.EXTERNAL_")
- || action.startsWith("android.bluetooth.")
- || action.equals(Intent.ACTION_SHUTDOWN)) {
- if (DEBUG_BROADCAST) {
- Slog.wtf(TAG,
- "System internals registering for " + filter.toLongString()
- + " with app priority; this will race with apps!",
- new Throwable());
- }
-
- // When undefined, assume that system internals need
- // to hear about the event first; they can use
- // SYSTEM_LOW_PRIORITY if they need to hear last
- if (priority == 0) {
- filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- }
- break;
- }
- }
- }
- }
-
- Iterator<String> actions = filter.actionsIterator();
- if (actions == null) {
- ArrayList<String> noAction = new ArrayList<String>(1);
- noAction.add(null);
- actions = noAction.iterator();
- }
- boolean onlyProtectedBroadcasts = true;
-
- // Collect stickies of users and check if broadcast is only registered for protected
- // broadcasts
- int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
- synchronized (mStickyBroadcasts) {
- while (actions.hasNext()) {
- String action = actions.next();
- for (int id : userIds) {
- ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
- mStickyBroadcasts.get(id);
- if (stickies != null) {
- ArrayList<StickyBroadcast> broadcasts = stickies.get(action);
- if (broadcasts != null) {
- if (stickyBroadcasts == null) {
- stickyBroadcasts = new ArrayList<>();
- }
- stickyBroadcasts.addAll(broadcasts);
- }
- }
- }
- if (onlyProtectedBroadcasts) {
- try {
- onlyProtectedBroadcasts &=
- AppGlobals.getPackageManager().isProtectedBroadcast(action);
- } catch (RemoteException e) {
- onlyProtectedBroadcasts = false;
- Slog.w(TAG, "Remote exception", e);
- }
- }
- }
- }
-
- if (Process.isSdkSandboxUid(Binder.getCallingUid())) {
- SdkSandboxManagerLocal sdkSandboxManagerLocal =
- LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
- if (sdkSandboxManagerLocal == null) {
- throw new IllegalStateException("SdkSandboxManagerLocal not found when checking"
- + " whether SDK sandbox uid can register to broadcast receivers.");
- }
- if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver(
- /*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) {
- throw new SecurityException("SDK sandbox not allowed to register receiver"
- + " with the given IntentFilter: " + filter.toLongString());
- }
- }
-
- // If the change is enabled, but neither exported or not exported is set, we need to log
- // an error so the consumer can know to explicitly set the value for their flag.
- // If the caller is registering for a sticky broadcast with a null receiver, we won't
- // require a flag
- final boolean explicitExportStateDefined =
- (flags & (Context.RECEIVER_EXPORTED | Context.RECEIVER_NOT_EXPORTED)) != 0;
- if (((flags & Context.RECEIVER_EXPORTED) != 0) && (
- (flags & Context.RECEIVER_NOT_EXPORTED) != 0)) {
- throw new IllegalArgumentException(
- "Receiver can't specify both RECEIVER_EXPORTED and RECEIVER_NOT_EXPORTED"
- + "flag");
- }
-
- // Don't enforce the flag check if we're EITHER registering for only protected
- // broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
- // not be used generally, so we will be marking them as exported by default
- boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
- DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid);
-
- // A receiver that is visible to instant apps must also be exported.
- final boolean unexportedReceiverVisibleToInstantApps =
- ((flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0) && (
- (flags & Context.RECEIVER_NOT_EXPORTED) != 0);
- if (unexportedReceiverVisibleToInstantApps && requireExplicitFlagForDynamicReceivers) {
- throw new IllegalArgumentException(
- "Receiver can't specify both RECEIVER_VISIBLE_TO_INSTANT_APPS and "
- + "RECEIVER_NOT_EXPORTED flag");
- }
-
- if (!onlyProtectedBroadcasts) {
- if (receiver == null && !explicitExportStateDefined) {
- // sticky broadcast, no flag specified (flag isn't required)
- flags |= Context.RECEIVER_EXPORTED;
- } else if (requireExplicitFlagForDynamicReceivers && !explicitExportStateDefined) {
- throw new SecurityException(
- callerPackage + ": One of RECEIVER_EXPORTED or "
- + "RECEIVER_NOT_EXPORTED should be specified when a receiver "
- + "isn't being registered exclusively for system broadcasts");
- // Assume default behavior-- flag check is not enforced
- } else if (!requireExplicitFlagForDynamicReceivers && (
- (flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
- // Change is not enabled, assume exported unless otherwise specified.
- flags |= Context.RECEIVER_EXPORTED;
- }
- } else if ((flags & Context.RECEIVER_NOT_EXPORTED) == 0) {
- flags |= Context.RECEIVER_EXPORTED;
- }
-
- // Dynamic receivers are exported by default for versions prior to T
- final boolean exported = (flags & Context.RECEIVER_EXPORTED) != 0;
-
- ArrayList<StickyBroadcast> allSticky = null;
- if (stickyBroadcasts != null) {
- final ContentResolver resolver = mContext.getContentResolver();
- // Look for any matching sticky broadcasts...
- for (int i = 0, N = stickyBroadcasts.size(); i < N; i++) {
- final StickyBroadcast broadcast = stickyBroadcasts.get(i);
- Intent intent = broadcast.intent;
- // Don't provided intents that aren't available to instant apps.
- if (instantApp &&
- (intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) == 0) {
- continue;
- }
- // If intent has scheme "content", it will need to access
- // provider that needs to lock mProviderMap in ActivityThread
- // and also it may need to wait application response, so we
- // cannot lock ActivityManagerService here.
- final int match;
- if (Flags.avoidResolvingType()) {
- match = filter.match(intent.getAction(), broadcast.resolvedDataType,
- intent.getScheme(), intent.getData(), intent.getCategories(),
- TAG, false /* supportsWildcards */, null /* ignoreActions */,
- intent.getExtras());
- } else {
- match = filter.match(resolver, intent, true, TAG);
- }
- if (match >= 0) {
- if (allSticky == null) {
- allSticky = new ArrayList<>();
- }
- allSticky.add(broadcast);
- }
- }
- }
-
- // The first sticky in the list is returned directly back to the client.
- Intent sticky = allSticky != null ? allSticky.get(0).intent : null;
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);
- if (receiver == null) {
- return sticky;
- }
-
- // SafetyNet logging for b/177931370. If any process other than system_server tries to
- // listen to this broadcast action, then log it.
- if (callingPid != Process.myPid()) {
- if (filter.hasAction("com.android.server.net.action.SNOOZE_WARNING")
- || filter.hasAction("com.android.server.net.action.SNOOZE_RAPID")) {
- EventLog.writeEvent(0x534e4554, "177931370", callingUid, "");
- }
- }
-
- synchronized (this) {
- IApplicationThread thread;
- if (callerApp != null && ((thread = callerApp.getThread()) == null
- || thread.asBinder() != caller.asBinder())) {
- // Original caller already died
- return null;
- }
- ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
- if (rl == null) {
- rl = new ReceiverList(this, callerApp, callingPid, callingUid,
- userId, receiver);
- if (rl.app != null) {
- final int totalReceiversForApp = rl.app.mReceivers.numberOfReceivers();
- if (totalReceiversForApp >= MAX_RECEIVERS_ALLOWED_PER_APP) {
- throw new IllegalStateException("Too many receivers, total of "
- + totalReceiversForApp + ", registered for pid: "
- + rl.pid + ", callerPackage: " + callerPackage);
- }
- rl.app.mReceivers.addReceiver(rl);
- } else {
- try {
- receiver.asBinder().linkToDeath(rl, 0);
- } catch (RemoteException e) {
- return sticky;
- }
- rl.linkedToDeath = true;
- }
- mRegisteredReceivers.put(receiver.asBinder(), rl);
- } else if (rl.uid != callingUid) {
- throw new IllegalArgumentException(
- "Receiver requested to register for uid " + callingUid
- + " was previously registered for uid " + rl.uid
- + " callerPackage is " + callerPackage);
- } else if (rl.pid != callingPid) {
- throw new IllegalArgumentException(
- "Receiver requested to register for pid " + callingPid
- + " was previously registered for pid " + rl.pid
- + " callerPackage is " + callerPackage);
- } else if (rl.userId != userId) {
- throw new IllegalArgumentException(
- "Receiver requested to register for user " + userId
- + " was previously registered for user " + rl.userId
- + " callerPackage is " + callerPackage);
- }
- BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
- receiverId, permission, callingUid, userId, instantApp, visibleToInstantApps,
- exported);
- if (rl.containsFilter(filter)) {
- Slog.w(TAG, "Receiver with filter " + filter
- + " already registered for pid " + rl.pid
- + ", callerPackage is " + callerPackage);
- } else {
- rl.add(bf);
- if (!bf.debugCheck()) {
- Slog.w(TAG, "==> For Dynamic broadcast");
- }
- mReceiverResolver.addFilter(getPackageManagerInternal().snapshot(), bf);
- }
-
- // Enqueue broadcasts for all existing stickies that match
- // this filter.
- if (allSticky != null) {
- ArrayList receivers = new ArrayList();
- receivers.add(bf);
- sticky = null;
-
- final int stickyCount = allSticky.size();
- for (int i = 0; i < stickyCount; i++) {
- final StickyBroadcast broadcast = allSticky.get(i);
- final int originalStickyCallingUid = allSticky.get(i).originalCallingUid;
- // TODO(b/281889567): consider using checkComponentPermission instead of
- // canAccessUnexportedComponents
- if (sticky == null && (exported || originalStickyCallingUid == callingUid
- || ActivityManager.canAccessUnexportedComponents(
- originalStickyCallingUid))) {
- sticky = broadcast.intent;
- }
- BroadcastQueue queue = mBroadcastQueue;
- BroadcastRecord r = new BroadcastRecord(queue, broadcast.intent, null,
- null, null, -1, -1, false, null, null, null, null, OP_NONE,
- BroadcastOptions.makeWithDeferUntilActive(broadcast.deferUntilActive),
- receivers, null, null, 0, null, null, false, true, true, -1,
- originalStickyCallingUid, BackgroundStartPrivileges.NONE,
- false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
- null /* filterExtrasForReceiver */,
- broadcast.originalCallingAppProcessState);
- queue.enqueueBroadcastLocked(r);
- }
- }
-
- return sticky;
- }
+ return mBroadcastController.registerReceiverWithFeature(caller, callerPackage,
+ callerFeatureId, receiverId, receiver, filter, permission, userId, flags);
}
public void unregisterReceiver(IIntentReceiver receiver) {
- traceUnregistrationBegin(receiver);
- try {
- unregisterReceiverTraced(receiver);
- } finally {
- traceUnregistrationEnd();
- }
- }
-
- private static void traceUnregistrationBegin(IIntentReceiver receiver) {
- if (!Flags.traceReceiverRegistration()) {
- return;
- }
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- TextUtils.formatSimple("unregisterReceiver: %d/%s", Binder.getCallingUid(),
- receiver == null ? "null" : receiver.asBinder()));
- }
- }
-
- private static void traceUnregistrationEnd() {
- if (!Flags.traceReceiverRegistration()) {
- return;
- }
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- }
- }
-
- private void unregisterReceiverTraced(IIntentReceiver receiver) {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Unregister receiver: " + receiver);
-
- final long origId = Binder.clearCallingIdentity();
- try {
- boolean doTrim = false;
- synchronized(this) {
- ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
- if (rl != null) {
- final BroadcastRecord r = rl.curBroadcast;
- if (r != null) {
- final boolean doNext = r.queue.finishReceiverLocked(
- rl.app, r.resultCode, r.resultData, r.resultExtras,
- r.resultAbort, false);
- if (doNext) {
- doTrim = true;
- }
- }
- if (rl.app != null) {
- rl.app.mReceivers.removeReceiver(rl);
- }
- removeReceiverLocked(rl);
- if (rl.linkedToDeath) {
- rl.linkedToDeath = false;
- rl.receiver.asBinder().unlinkToDeath(rl, 0);
- }
- }
-
- // If we actually concluded any broadcasts, we might now be able
- // to trim the recipients' apps from our working set
- if (doTrim) {
- trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
- return;
- }
- }
-
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
-
- void removeReceiverLocked(ReceiverList rl) {
- mRegisteredReceivers.remove(rl.receiver.asBinder());
- for (int i = rl.size() - 1; i >= 0; i--) {
- mReceiverResolver.removeFilter(rl.get(i));
- }
- }
-
- private final void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
- mProcessList.sendPackageBroadcastLocked(cmd, packages, userId);
- }
-
- private List<ResolveInfo> collectReceiverComponents(
- Intent intent, String resolvedType, int callingUid, int callingPid,
- int[] users, int[] broadcastAllowList) {
- // TODO: come back and remove this assumption to triage all broadcasts
- long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;
-
- List<ResolveInfo> receivers = null;
- HashSet<ComponentName> singleUserReceivers = null;
- boolean scannedFirstReceivers = false;
- for (int user : users) {
- // Skip users that have Shell restrictions
- if (callingUid == SHELL_UID
- && mUserController.hasUserRestriction(
- UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
- continue;
- }
- List<ResolveInfo> newReceivers = mPackageManagerInt.queryIntentReceivers(
- intent, resolvedType, pmFlags, callingUid, callingPid, user, /* forSend */true);
- if (user != UserHandle.USER_SYSTEM && newReceivers != null) {
- // If this is not the system user, we need to check for
- // any receivers that should be filtered out.
- for (int i = 0; i < newReceivers.size(); i++) {
- ResolveInfo ri = newReceivers.get(i);
- if ((ri.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
- newReceivers.remove(i);
- i--;
- }
- }
- }
- // Replace the alias receivers with their targets.
- if (newReceivers != null) {
- for (int i = newReceivers.size() - 1; i >= 0; i--) {
- final ResolveInfo ri = newReceivers.get(i);
- final Resolution<ResolveInfo> resolution =
- mComponentAliasResolver.resolveReceiver(intent, ri, resolvedType,
- pmFlags, user, callingUid, callingPid);
- if (resolution == null) {
- // It was an alias, but the target was not found.
- newReceivers.remove(i);
- continue;
- }
- if (resolution.isAlias()) {
- newReceivers.set(i, resolution.getTarget());
- }
- }
- }
- if (newReceivers != null && newReceivers.size() == 0) {
- newReceivers = null;
- }
-
- if (receivers == null) {
- receivers = newReceivers;
- } else if (newReceivers != null) {
- // We need to concatenate the additional receivers
- // found with what we have do far. This would be easy,
- // but we also need to de-dup any receivers that are
- // singleUser.
- if (!scannedFirstReceivers) {
- // Collect any single user receivers we had already retrieved.
- scannedFirstReceivers = true;
- for (int i = 0; i < receivers.size(); i++) {
- ResolveInfo ri = receivers.get(i);
- if ((ri.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
- ComponentName cn = new ComponentName(
- ri.activityInfo.packageName, ri.activityInfo.name);
- if (singleUserReceivers == null) {
- singleUserReceivers = new HashSet<ComponentName>();
- }
- singleUserReceivers.add(cn);
- }
- }
- }
- // Add the new results to the existing results, tracking
- // and de-dupping single user receivers.
- for (int i = 0; i < newReceivers.size(); i++) {
- ResolveInfo ri = newReceivers.get(i);
- if ((ri.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
- ComponentName cn = new ComponentName(
- ri.activityInfo.packageName, ri.activityInfo.name);
- if (singleUserReceivers == null) {
- singleUserReceivers = new HashSet<ComponentName>();
- }
- if (!singleUserReceivers.contains(cn)) {
- singleUserReceivers.add(cn);
- receivers.add(ri);
- }
- } else {
- receivers.add(ri);
- }
- }
- }
- }
- if (receivers != null && broadcastAllowList != null) {
- for (int i = receivers.size() - 1; i >= 0; i--) {
- final int receiverAppId = UserHandle.getAppId(
- receivers.get(i).activityInfo.applicationInfo.uid);
- if (receiverAppId >= Process.FIRST_APPLICATION_UID
- && Arrays.binarySearch(broadcastAllowList, receiverAppId) < 0) {
- receivers.remove(i);
- }
- }
- }
- return receivers;
- }
-
- private void checkBroadcastFromSystem(Intent intent, ProcessRecord callerApp,
- String callerPackage, int callingUid, boolean isProtectedBroadcast, List receivers) {
- if ((intent.getFlags() & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
- // Don't yell about broadcasts sent via shell
- return;
- }
-
- final String action = intent.getAction();
- if (isProtectedBroadcast
- || Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
- || Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(action)
- || Intent.ACTION_MEDIA_BUTTON.equals(action)
- || Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action)
- || Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(action)
- || Intent.ACTION_MASTER_CLEAR.equals(action)
- || Intent.ACTION_FACTORY_RESET.equals(action)
- || AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
- || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)
- || TelephonyManager.ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE.equals(action)
- || SuggestionSpan.ACTION_SUGGESTION_PICKED.equals(action)
- || AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION.equals(action)
- || AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION.equals(action)) {
- // Broadcast is either protected, or it's a public action that
- // we've relaxed, so it's fine for system internals to send.
- return;
- }
-
- // This broadcast may be a problem... but there are often system components that
- // want to send an internal broadcast to themselves, which is annoying to have to
- // explicitly list each action as a protected broadcast, so we will check for that
- // one safe case and allow it: an explicit broadcast, only being received by something
- // that has protected itself.
- if (intent.getPackage() != null || intent.getComponent() != null) {
- if (receivers == null || receivers.size() == 0) {
- // Intent is explicit and there's no receivers.
- // This happens, e.g. , when a system component sends a broadcast to
- // its own runtime receiver, and there's no manifest receivers for it,
- // because this method is called twice for each broadcast,
- // for runtime receivers and manifest receivers and the later check would find
- // no receivers.
- return;
- }
- boolean allProtected = true;
- for (int i = receivers.size()-1; i >= 0; i--) {
- Object target = receivers.get(i);
- if (target instanceof ResolveInfo) {
- ResolveInfo ri = (ResolveInfo)target;
- if (ri.activityInfo.exported && ri.activityInfo.permission == null) {
- allProtected = false;
- break;
- }
- } else {
- BroadcastFilter bf = (BroadcastFilter)target;
- if (bf.exported && bf.requiredPermission == null) {
- allProtected = false;
- break;
- }
- }
- }
- if (allProtected) {
- // All safe!
- return;
- }
- }
-
- // The vast majority of broadcasts sent from system internals
- // should be protected to avoid security holes, so yell loudly
- // to ensure we examine these cases.
- if (callerApp != null) {
- Log.wtf(TAG, "Sending non-protected broadcast " + action
- + " from system " + callerApp.toShortString() + " pkg " + callerPackage,
- new Throwable());
- } else {
- Log.wtf(TAG, "Sending non-protected broadcast " + action
- + " from system uid " + UserHandle.formatUid(callingUid)
- + " pkg " + callerPackage,
- new Throwable());
- }
- }
-
- // Apply permission policy around the use of specific broadcast options
- void enforceBroadcastOptionPermissionsInternal(
- @Nullable Bundle options, int callingUid) {
- enforceBroadcastOptionPermissionsInternal(BroadcastOptions.fromBundleNullable(options),
- callingUid);
- }
-
- void enforceBroadcastOptionPermissionsInternal(
- @Nullable BroadcastOptions options, int callingUid) {
- if (options != null && callingUid != Process.SYSTEM_UID) {
- if (options.isAlarmBroadcast()) {
- if (DEBUG_BROADCAST_LIGHT) {
- Slog.w(TAG, "Non-system caller " + callingUid
- + " may not flag broadcast as alarm");
- }
- throw new SecurityException(
- "Non-system callers may not flag broadcasts as alarm");
- }
- if (options.isInteractive()) {
- enforceCallingPermission(
- android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE,
- "setInteractive");
- }
- }
+ mBroadcastController.unregisterReceiver(receiver);
}
@GuardedBy("this")
@@ -15296,1033 +14231,14 @@
String[] excludedPackages, int appOp, Bundle bOptions, boolean ordered,
boolean sticky, int callingPid,
int callingUid, int realCallingUid, int realCallingPid, int userId) {
- return broadcastIntentLocked(callerApp, callerPackage, callerFeatureId, intent,
- resolvedType, null, resultTo, resultCode, resultData, resultExtras,
+ return mBroadcastController.broadcastIntentLocked(callerApp, callerPackage, callerFeatureId,
+ intent, resolvedType, null, resultTo, resultCode, resultData, resultExtras,
requiredPermissions, excludedPermissions, excludedPackages, appOp, bOptions,
ordered, sticky, callingPid, callingUid, realCallingUid, realCallingPid, userId,
BackgroundStartPrivileges.NONE,
null /* broadcastAllowList */, null /* filterExtrasForReceiver */);
}
- @GuardedBy("this")
- final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage,
- @Nullable String callerFeatureId, Intent intent, String resolvedType,
- ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
- Bundle resultExtras, String[] requiredPermissions,
- String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
- boolean ordered, boolean sticky, int callingPid, int callingUid,
- int realCallingUid, int realCallingPid, int userId,
- BackgroundStartPrivileges backgroundStartPrivileges,
- @Nullable int[] broadcastAllowList,
- @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
- final int cookie = traceBroadcastIntentBegin(intent, resultTo, ordered, sticky,
- callingUid, realCallingUid, userId);
- try {
- final BroadcastSentEventRecord broadcastSentEventRecord =
- new BroadcastSentEventRecord();
- final int res = broadcastIntentLockedTraced(callerApp, callerPackage, callerFeatureId,
- intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
- resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
- appOp, BroadcastOptions.fromBundleNullable(bOptions), ordered, sticky,
- callingPid, callingUid, realCallingUid, realCallingPid, userId,
- backgroundStartPrivileges, broadcastAllowList, filterExtrasForReceiver,
- broadcastSentEventRecord);
- broadcastSentEventRecord.setResult(res);
- broadcastSentEventRecord.logToStatsd();
- return res;
- } finally {
- traceBroadcastIntentEnd(cookie);
- }
- }
-
- private static int traceBroadcastIntentBegin(Intent intent, IIntentReceiver resultTo,
- boolean ordered, boolean sticky, int callingUid, int realCallingUid, int userId) {
- if (!Flags.traceReceiverRegistration()) {
- return BroadcastQueue.traceBegin("broadcastIntentLockedTraced");
- }
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- final StringBuilder sb = new StringBuilder("broadcastIntent: ");
- sb.append(callingUid); sb.append('/');
- final String action = intent.getAction();
- sb.append(action == null ? null : action); sb.append('/');
- sb.append("0x"); sb.append(Integer.toHexString(intent.getFlags())); sb.append('/');
- sb.append(ordered ? "O" : "_");
- sb.append(sticky ? "S" : "_");
- sb.append(resultTo != null ? "C" : "_");
- sb.append('/');
- sb.append('u'); sb.append(userId);
- if (callingUid != realCallingUid) {
- sb.append('/');
- sb.append("sender="); sb.append(realCallingUid);
- }
- return BroadcastQueue.traceBegin(sb.toString());
- }
- return 0;
- }
-
- private static void traceBroadcastIntentEnd(int cookie) {
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- BroadcastQueue.traceEnd(cookie);
- }
- }
-
- @GuardedBy("this")
- final int broadcastIntentLockedTraced(ProcessRecord callerApp, String callerPackage,
- @Nullable String callerFeatureId, Intent intent, String resolvedType,
- ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
- Bundle resultExtras, String[] requiredPermissions,
- String[] excludedPermissions, String[] excludedPackages, int appOp,
- BroadcastOptions brOptions, boolean ordered, boolean sticky, int callingPid,
- int callingUid, int realCallingUid, int realCallingPid, int userId,
- BackgroundStartPrivileges backgroundStartPrivileges,
- @Nullable int[] broadcastAllowList,
- @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
- @NonNull BroadcastSentEventRecord broadcastSentEventRecord) {
- // Ensure all internal loopers are registered for idle checks
- BroadcastLoopers.addMyLooper();
-
- if (Process.isSdkSandboxUid(realCallingUid)) {
- final SdkSandboxManagerLocal sdkSandboxManagerLocal = LocalManagerRegistry.getManager(
- SdkSandboxManagerLocal.class);
- if (sdkSandboxManagerLocal == null) {
- throw new IllegalStateException("SdkSandboxManagerLocal not found when sending"
- + " a broadcast from an SDK sandbox uid.");
- }
- if (!sdkSandboxManagerLocal.canSendBroadcast(intent)) {
- throw new SecurityException(
- "Intent " + intent.getAction() + " may not be broadcast from an SDK sandbox"
- + " uid. Given caller package " + callerPackage + " (pid=" + callingPid
- + ", realCallingUid=" + realCallingUid + ", callingUid= " + callingUid
- + ")");
- }
- }
-
- if ((resultTo != null) && (resultToApp == null)) {
- if (resultTo.asBinder() instanceof BinderProxy) {
- // Warn when requesting results without a way to deliver them
- Slog.wtf(TAG, "Sending broadcast " + intent.getAction()
- + " with resultTo requires resultToApp", new Throwable());
- } else {
- // If not a BinderProxy above, then resultTo is an in-process
- // receiver, so splice in system_server process
- resultToApp = getProcessRecordLocked("system", SYSTEM_UID);
- }
- }
-
- intent = new Intent(intent);
- broadcastSentEventRecord.setIntent(intent);
- broadcastSentEventRecord.setOriginalIntentFlags(intent.getFlags());
- broadcastSentEventRecord.setSenderUid(callingUid);
- broadcastSentEventRecord.setRealSenderUid(realCallingUid);
- broadcastSentEventRecord.setSticky(sticky);
- broadcastSentEventRecord.setOrdered(ordered);
- broadcastSentEventRecord.setResultRequested(resultTo != null);
- final int callerAppProcessState = getRealProcessStateLocked(callerApp, realCallingPid);
- broadcastSentEventRecord.setSenderProcState(callerAppProcessState);
- broadcastSentEventRecord.setSenderUidState(getRealUidStateLocked(callerApp,
- realCallingPid));
-
- final boolean callerInstantApp = isInstantApp(callerApp, callerPackage, callingUid);
- // Instant Apps cannot use FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS
- if (callerInstantApp) {
- intent.setFlags(intent.getFlags() & ~Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
- }
-
- if (userId == UserHandle.USER_ALL && broadcastAllowList != null) {
- Slog.e(TAG, "broadcastAllowList only applies when sending to individual users. "
- + "Assuming restrictive whitelist.");
- broadcastAllowList = new int[]{};
- }
-
- // By default broadcasts do not go to stopped apps.
- intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
-
- // If we have not finished booting, don't allow this to launch new processes.
- if (!mProcessesReady && (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- }
-
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
- (sticky ? "Broadcast sticky: ": "Broadcast: ") + intent
- + " ordered=" + ordered + " userid=" + userId
- + " options=" + (brOptions == null ? "null" : brOptions.toBundle()));
- if ((resultTo != null) && !ordered) {
- if (!UserHandle.isCore(callingUid)) {
- String msg = "Unauthorized unordered resultTo broadcast "
- + intent + " sent from uid " + callingUid;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
- }
-
- userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
- ALLOW_NON_FULL, "broadcast", callerPackage);
-
- // Make sure that the user who is receiving this broadcast or its parent is running.
- // If not, we will just skip it. Make an exception for shutdown broadcasts, upgrade steps.
- if (userId != UserHandle.USER_ALL && !mUserController.isUserOrItsParentRunning(userId)) {
- if ((callingUid != SYSTEM_UID
- || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
- && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
- Slog.w(TAG, "Skipping broadcast of " + intent
- + ": user " + userId + " and its parent (if any) are stopped");
- scheduleCanceledResultTo(resultToApp, resultTo, intent, userId,
- brOptions, callingUid, callerPackage);
- return ActivityManager.BROADCAST_FAILED_USER_STOPPED;
- }
- }
-
- final String action = intent.getAction();
- if (brOptions != null) {
- if (brOptions.getTemporaryAppAllowlistDuration() > 0) {
- // See if the caller is allowed to do this. Note we are checking against
- // the actual real caller (not whoever provided the operation as say a
- // PendingIntent), because that who is actually supplied the arguments.
- if (checkComponentPermission(CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
- realCallingPid, realCallingUid, -1, true)
- != PackageManager.PERMISSION_GRANTED
- && checkComponentPermission(START_ACTIVITIES_FROM_BACKGROUND,
- realCallingPid, realCallingUid, -1, true)
- != PackageManager.PERMISSION_GRANTED
- && checkComponentPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND,
- realCallingPid, realCallingUid, -1, true)
- != PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: " + intent.getAction()
- + " broadcast from " + callerPackage + " (pid=" + callingPid
- + ", uid=" + callingUid + ")"
- + " requires "
- + CHANGE_DEVICE_IDLE_TEMP_WHITELIST + " or "
- + START_ACTIVITIES_FROM_BACKGROUND + " or "
- + START_FOREGROUND_SERVICES_FROM_BACKGROUND;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
- }
- if (brOptions.isDontSendToRestrictedApps()
- && !isUidActiveLOSP(callingUid)
- && isBackgroundRestrictedNoCheck(callingUid, callerPackage)) {
- Slog.i(TAG, "Not sending broadcast " + action + " - app " + callerPackage
- + " has background restrictions");
- return ActivityManager.START_CANCELED;
- }
- if (brOptions.allowsBackgroundActivityStarts()) {
- // See if the caller is allowed to do this. Note we are checking against
- // the actual real caller (not whoever provided the operation as say a
- // PendingIntent), because that who is actually supplied the arguments.
- if (checkComponentPermission(
- android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
- realCallingPid, realCallingUid, -1, true)
- != PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: " + intent.getAction()
- + " broadcast from " + callerPackage + " (pid=" + callingPid
- + ", uid=" + callingUid + ")"
- + " requires "
- + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- } else {
- // We set the token to null since if it wasn't for it we'd allow anyway here
- backgroundStartPrivileges = BackgroundStartPrivileges.ALLOW_BAL;
- }
- }
-
- if (brOptions.getIdForResponseEvent() > 0) {
- enforcePermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS,
- callingPid, callingUid, "recordResponseEventWhileInBackground");
- }
- }
-
- // Verify that protected broadcasts are only being sent by system code,
- // and that system code is only sending protected broadcasts.
- final boolean isProtectedBroadcast;
- try {
- isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
- } catch (RemoteException e) {
- Slog.w(TAG, "Remote exception", e);
- scheduleCanceledResultTo(resultToApp, resultTo, intent,
- userId, brOptions, callingUid, callerPackage);
- return ActivityManager.BROADCAST_SUCCESS;
- }
-
- final boolean isCallerSystem;
- switch (UserHandle.getAppId(callingUid)) {
- case ROOT_UID:
- case SYSTEM_UID:
- case PHONE_UID:
- case BLUETOOTH_UID:
- case NFC_UID:
- case SE_UID:
- case NETWORK_STACK_UID:
- isCallerSystem = true;
- break;
- default:
- isCallerSystem = (callerApp != null) && callerApp.isPersistent();
- break;
- }
-
- // First line security check before anything else: stop non-system apps from
- // sending protected broadcasts.
- if (!isCallerSystem) {
- if (isProtectedBroadcast) {
- String msg = "Permission Denial: not allowed to send broadcast "
- + action + " from pid="
- + callingPid + ", uid=" + callingUid;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
-
- } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
- || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
- // Special case for compatibility: we don't want apps to send this,
- // but historically it has not been protected and apps may be using it
- // to poke their own app widget. So, instead of making it protected,
- // just limit it to the caller.
- if (callerPackage == null) {
- String msg = "Permission Denial: not allowed to send broadcast "
- + action + " from unknown caller.";
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- } else if (intent.getComponent() != null) {
- // They are good enough to send to an explicit component... verify
- // it is being sent to the calling app.
- if (!intent.getComponent().getPackageName().equals(
- callerPackage)) {
- String msg = "Permission Denial: not allowed to send broadcast "
- + action + " to "
- + intent.getComponent().getPackageName() + " from "
- + callerPackage;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
- } else {
- // Limit broadcast to their own package.
- intent.setPackage(callerPackage);
- }
- }
- }
-
- boolean timeoutExempt = false;
-
- if (action != null) {
- if (getBackgroundLaunchBroadcasts().contains(action)) {
- if (DEBUG_BACKGROUND_CHECK) {
- Slog.i(TAG, "Broadcast action " + action + " forcing include-background");
- }
- intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- }
-
- // TODO: b/329211459 - Remove this after background remote intent is fixed.
- if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
- && getWearRemoteIntentAction().equals(action)) {
- final int callerProcState = callerApp != null
- ? callerApp.getCurProcState()
- : ActivityManager.PROCESS_STATE_NONEXISTENT;
- if (ActivityManager.RunningAppProcessInfo.procStateToImportance(callerProcState)
- > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
- return ActivityManager.START_CANCELED;
- }
- }
-
- switch (action) {
- case Intent.ACTION_MEDIA_SCANNER_SCAN_FILE:
- UserManagerInternal umInternal = LocalServices.getService(
- UserManagerInternal.class);
- UserInfo userInfo = umInternal.getUserInfo(userId);
- if (userInfo != null && userInfo.isCloneProfile()) {
- userId = umInternal.getProfileParentId(userId);
- }
- break;
- case Intent.ACTION_UID_REMOVED:
- case Intent.ACTION_PACKAGE_REMOVED:
- case Intent.ACTION_PACKAGE_CHANGED:
- case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
- case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
- case Intent.ACTION_PACKAGES_SUSPENDED:
- case Intent.ACTION_PACKAGES_UNSUSPENDED:
- // Handle special intents: if this broadcast is from the package
- // manager about a package being removed, we need to remove all of
- // its activities from the history stack.
- if (checkComponentPermission(
- android.Manifest.permission.BROADCAST_PACKAGE_REMOVED,
- callingPid, callingUid, -1, true)
- != PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: " + intent.getAction()
- + " broadcast from " + callerPackage + " (pid=" + callingPid
- + ", uid=" + callingUid + ")"
- + " requires "
- + android.Manifest.permission.BROADCAST_PACKAGE_REMOVED;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
- switch (action) {
- case Intent.ACTION_UID_REMOVED:
- final int uid = getUidFromIntent(intent);
- if (uid >= 0) {
- mBatteryStatsService.removeUid(uid);
- if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
- mAppOpsService.resetAllModes(UserHandle.getUserId(uid),
- intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
- } else {
- mAppOpsService.uidRemoved(uid);
- mServices.onUidRemovedLocked(uid);
- }
- }
- break;
- case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
- // If resources are unavailable just force stop all those packages
- // and flush the attribute cache as well.
- String list[] =
- intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- if (list != null && list.length > 0) {
- for (int i = 0; i < list.length; i++) {
- forceStopPackageLocked(list[i], -1, false, true, true,
- false, false, false, userId, "storage unmount");
- }
- mAtmInternal.cleanupRecentTasksForUser(UserHandle.USER_ALL);
- sendPackageBroadcastLocked(
- ApplicationThreadConstants.EXTERNAL_STORAGE_UNAVAILABLE,
- list, userId);
- }
- break;
- case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
- mAtmInternal.cleanupRecentTasksForUser(UserHandle.USER_ALL);
- break;
- case Intent.ACTION_PACKAGE_REMOVED:
- case Intent.ACTION_PACKAGE_CHANGED:
- Uri data = intent.getData();
- String ssp;
- if (data != null && (ssp=data.getSchemeSpecificPart()) != null) {
- boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals(action);
- final boolean replacing =
- intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
- final boolean killProcess =
- !intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false);
- final boolean fullUninstall = removed && !replacing;
-
- if (removed) {
- if (killProcess) {
- forceStopPackageLocked(ssp, UserHandle.getAppId(
- intent.getIntExtra(Intent.EXTRA_UID, -1)),
- false, true, true, false, fullUninstall, false,
- userId, "pkg removed");
- getPackageManagerInternal()
- .onPackageProcessKilledForUninstall(ssp);
- } else {
- // Kill any app zygotes always, since they can't fork new
- // processes with references to the old code
- forceStopAppZygoteLocked(ssp, UserHandle.getAppId(
- intent.getIntExtra(Intent.EXTRA_UID, -1)),
- userId);
- }
- final int cmd = killProcess
- ? ApplicationThreadConstants.PACKAGE_REMOVED
- : ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL;
- sendPackageBroadcastLocked(cmd,
- new String[] {ssp}, userId);
- if (fullUninstall) {
- // Remove all permissions granted from/to this package
- mUgmInternal.removeUriPermissionsForPackage(ssp, userId,
- true, false);
-
- mAtmInternal.removeRecentTasksByPackageName(ssp, userId);
-
- mServices.forceStopPackageLocked(ssp, userId);
- mAtmInternal.onPackageUninstalled(ssp, userId);
- mBatteryStatsService.notePackageUninstalled(ssp);
- }
- } else {
- if (killProcess) {
- int reason;
- int subReason;
- if (replacing) {
- reason = ApplicationExitInfo.REASON_PACKAGE_UPDATED;
- subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
- } else {
- reason =
- ApplicationExitInfo.REASON_PACKAGE_STATE_CHANGE;
- subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
- }
-
- final int extraUid = intent.getIntExtra(Intent.EXTRA_UID,
- -1);
- synchronized (mProcLock) {
- mProcessList.killPackageProcessesLSP(ssp,
- UserHandle.getAppId(extraUid),
- userId, ProcessList.INVALID_ADJ,
- reason,
- subReason,
- "change " + ssp);
- }
- }
- cleanupDisabledPackageComponentsLocked(ssp, userId,
- intent.getStringArrayExtra(
- Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST));
- mServices.schedulePendingServiceStartLocked(ssp, userId);
- }
- }
- break;
- case Intent.ACTION_PACKAGES_SUSPENDED:
- case Intent.ACTION_PACKAGES_UNSUSPENDED:
- final boolean suspended = Intent.ACTION_PACKAGES_SUSPENDED.equals(
- intent.getAction());
- final String[] packageNames = intent.getStringArrayExtra(
- Intent.EXTRA_CHANGED_PACKAGE_LIST);
- final int userIdExtra = intent.getIntExtra(
- Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-
- mAtmInternal.onPackagesSuspendedChanged(packageNames, suspended,
- userIdExtra);
-
- final boolean quarantined = intent.getBooleanExtra(
- Intent.EXTRA_QUARANTINED, false);
- if (suspended && quarantined && packageNames != null) {
- for (int i = 0; i < packageNames.length; i++) {
- forceStopPackage(packageNames[i], userId,
- ActivityManager.FLAG_OR_STOPPED, "quarantined");
- }
- }
-
- break;
- }
- break;
- case Intent.ACTION_PACKAGE_REPLACED:
- {
- final Uri data = intent.getData();
- final String ssp;
- if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
- ApplicationInfo aInfo = null;
- try {
- aInfo = AppGlobals.getPackageManager()
- .getApplicationInfo(ssp, STOCK_PM_FLAGS, userId);
- } catch (RemoteException ignore) {}
- if (aInfo == null) {
- Slog.w(TAG, "Dropping ACTION_PACKAGE_REPLACED for non-existent pkg:"
- + " ssp=" + ssp + " data=" + data);
- scheduleCanceledResultTo(resultToApp, resultTo, intent,
- userId, brOptions, callingUid, callerPackage);
- return ActivityManager.BROADCAST_SUCCESS;
- }
- updateAssociationForApp(aInfo);
- mAtmInternal.onPackageReplaced(aInfo);
- mServices.updateServiceApplicationInfoLocked(aInfo);
- sendPackageBroadcastLocked(ApplicationThreadConstants.PACKAGE_REPLACED,
- new String[] {ssp}, userId);
- }
- break;
- }
- case Intent.ACTION_PACKAGE_ADDED:
- {
- // Special case for adding a package: by default turn on compatibility mode.
- Uri data = intent.getData();
- String ssp;
- if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
- final boolean replacing =
- intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
- mAtmInternal.onPackageAdded(ssp, replacing);
-
- try {
- ApplicationInfo ai = AppGlobals.getPackageManager().
- getApplicationInfo(ssp, STOCK_PM_FLAGS, 0);
- mBatteryStatsService.notePackageInstalled(ssp,
- ai != null ? ai.longVersionCode : 0);
- } catch (RemoteException e) {
- }
- }
- break;
- }
- case Intent.ACTION_PACKAGE_DATA_CLEARED:
- {
- Uri data = intent.getData();
- String ssp;
- if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
- mAtmInternal.onPackageDataCleared(ssp, userId);
- }
- break;
- }
- case Intent.ACTION_TIMEZONE_CHANGED:
- // If this is the time zone changed action, queue up a message that will reset
- // the timezone of all currently running processes. This message will get
- // queued up before the broadcast happens.
- mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
- break;
- case Intent.ACTION_TIME_CHANGED:
- // EXTRA_TIME_PREF_24_HOUR_FORMAT is optional so we must distinguish between
- // the tri-state value it may contain and "unknown".
- // For convenience we re-use the Intent extra values.
- final int NO_EXTRA_VALUE_FOUND = -1;
- final int timeFormatPreferenceMsgValue = intent.getIntExtra(
- Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT,
- NO_EXTRA_VALUE_FOUND /* defaultValue */);
- // Only send a message if the time preference is available.
- if (timeFormatPreferenceMsgValue != NO_EXTRA_VALUE_FOUND) {
- Message updateTimePreferenceMsg =
- mHandler.obtainMessage(UPDATE_TIME_PREFERENCE_MSG,
- timeFormatPreferenceMsgValue, 0);
- mHandler.sendMessage(updateTimePreferenceMsg);
- }
- mBatteryStatsService.noteCurrentTimeChanged();
- break;
- case ConnectivityManager.ACTION_CLEAR_DNS_CACHE:
- mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);
- break;
- case Proxy.PROXY_CHANGE_ACTION:
- mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG));
- break;
- case android.hardware.Camera.ACTION_NEW_PICTURE:
- case android.hardware.Camera.ACTION_NEW_VIDEO:
- // In N we just turned these off; in O we are turing them back on partly,
- // only for registered receivers. This will still address the main problem
- // (a spam of apps waking up when a picture is taken putting significant
- // memory pressure on the system at a bad point), while still allowing apps
- // that are already actively running to know about this happening.
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- break;
- case android.security.KeyChain.ACTION_TRUST_STORE_CHANGED:
- mHandler.sendEmptyMessage(HANDLE_TRUST_STORAGE_UPDATE_MSG);
- break;
- case "com.android.launcher.action.INSTALL_SHORTCUT":
- // As of O, we no longer support this broadcasts, even for pre-O apps.
- // Apps should now be using ShortcutManager.pinRequestShortcut().
- Log.w(TAG, "Broadcast " + action
- + " no longer supported. It will not be delivered.");
- scheduleCanceledResultTo(resultToApp, resultTo, intent,
- userId, brOptions, callingUid, callerPackage);
- return ActivityManager.BROADCAST_SUCCESS;
- case Intent.ACTION_PRE_BOOT_COMPLETED:
- timeoutExempt = true;
- break;
- case Intent.ACTION_CLOSE_SYSTEM_DIALOGS:
- if (!mAtmInternal.checkCanCloseSystemDialogs(callingPid, callingUid,
- callerPackage)) {
- scheduleCanceledResultTo(resultToApp, resultTo, intent,
- userId, brOptions, callingUid, callerPackage);
- // Returning success seems to be the pattern here
- return ActivityManager.BROADCAST_SUCCESS;
- }
- break;
- }
-
- if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
- Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
- Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
- final int uid = getUidFromIntent(intent);
- if (uid != -1) {
- final UidRecord uidRec = mProcessList.getUidRecordLOSP(uid);
- if (uidRec != null) {
- uidRec.updateHasInternetPermission();
- }
- }
- }
- }
-
- // Add to the sticky list if requested.
- if (sticky) {
- if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,
- callingPid, callingUid)
- != PackageManager.PERMISSION_GRANTED) {
- String msg =
- "Permission Denial: broadcastIntent() requesting a sticky broadcast from"
- + " pid="
- + callingPid
- + ", uid="
- + callingUid
- + " requires "
- + android.Manifest.permission.BROADCAST_STICKY;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
- if (requiredPermissions != null && requiredPermissions.length > 0) {
- Slog.w(TAG, "Can't broadcast sticky intent " + intent
- + " and enforce permissions " + Arrays.toString(requiredPermissions));
- scheduleCanceledResultTo(resultToApp, resultTo, intent,
- userId, brOptions, callingUid, callerPackage);
- return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;
- }
- if (intent.getComponent() != null) {
- throw new SecurityException(
- "Sticky broadcasts can't target a specific component");
- }
- synchronized (mStickyBroadcasts) {
- // We use userId directly here, since the "all" target is maintained
- // as a separate set of sticky broadcasts.
- if (userId != UserHandle.USER_ALL) {
- // But first, if this is not a broadcast to all users, then
- // make sure it doesn't conflict with an existing broadcast to
- // all users.
- ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(
- UserHandle.USER_ALL);
- if (stickies != null) {
- ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
- if (list != null) {
- int N = list.size();
- int i;
- for (i = 0; i < N; i++) {
- if (intent.filterEquals(list.get(i).intent)) {
- throw new IllegalArgumentException("Sticky broadcast " + intent
- + " for user " + userId
- + " conflicts with existing global broadcast");
- }
- }
- }
- }
- }
- ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
- mStickyBroadcasts.get(userId);
- if (stickies == null) {
- stickies = new ArrayMap<>();
- mStickyBroadcasts.put(userId, stickies);
- }
- ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
- if (list == null) {
- list = new ArrayList<>();
- stickies.put(intent.getAction(), list);
- }
- final boolean deferUntilActive = BroadcastRecord.calculateDeferUntilActive(
- callingUid, brOptions, resultTo, ordered,
- BroadcastRecord.calculateUrgent(intent, brOptions));
- final int stickiesCount = list.size();
- int i;
- for (i = 0; i < stickiesCount; i++) {
- if (intent.filterEquals(list.get(i).intent)) {
- // This sticky already exists, replace it.
- list.set(i, StickyBroadcast.create(new Intent(intent), deferUntilActive,
- callingUid, callerAppProcessState, resolvedType));
- break;
- }
- }
- if (i >= stickiesCount) {
- list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
- callingUid, callerAppProcessState, resolvedType));
- }
- }
- }
-
- int[] users;
- if (userId == UserHandle.USER_ALL) {
- // Caller wants broadcast to go to all started users.
- users = mUserController.getStartedUserArray();
- } else {
- // Caller wants broadcast to go to one specific user.
- users = new int[] {userId};
- }
-
- var args = new SaferIntentUtils.IntentArgs(intent, resolvedType,
- true /* isReceiver */, true /* resolveForStart */, callingUid, callingPid);
- args.platformCompat = mPlatformCompat;
-
- // Figure out who all will receive this broadcast.
- final int cookie = BroadcastQueue.traceBegin("queryReceivers");
- List receivers = null;
- List<BroadcastFilter> registeredReceivers = null;
- // Need to resolve the intent to interested receivers...
- if ((intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
- receivers = collectReceiverComponents(
- intent, resolvedType, callingUid, callingPid, users, broadcastAllowList);
- }
- if (intent.getComponent() == null) {
- final PackageDataSnapshot snapshot = getPackageManagerInternal().snapshot();
- if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
- // Query one target user at a time, excluding shell-restricted users
- for (int i = 0; i < users.length; i++) {
- if (mUserController.hasUserRestriction(
- UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {
- continue;
- }
- List<BroadcastFilter> registeredReceiversForUser =
- mReceiverResolver.queryIntent(snapshot, intent,
- resolvedType, false /*defaultOnly*/, users[i]);
- if (registeredReceivers == null) {
- registeredReceivers = registeredReceiversForUser;
- } else if (registeredReceiversForUser != null) {
- registeredReceivers.addAll(registeredReceiversForUser);
- }
- }
- } else {
- registeredReceivers = mReceiverResolver.queryIntent(snapshot, intent,
- resolvedType, false /*defaultOnly*/, userId);
- }
- if (registeredReceivers != null) {
- SaferIntentUtils.blockNullAction(args, registeredReceivers);
- }
- }
- BroadcastQueue.traceEnd(cookie);
-
- final boolean replacePending =
- (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
-
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing broadcast: " + intent.getAction()
- + " replacePending=" + replacePending);
- if (registeredReceivers != null && broadcastAllowList != null) {
- // if a uid whitelist was provided, remove anything in the application space that wasn't
- // in it.
- for (int i = registeredReceivers.size() - 1; i >= 0; i--) {
- final int owningAppId = UserHandle.getAppId(registeredReceivers.get(i).owningUid);
- if (owningAppId >= Process.FIRST_APPLICATION_UID
- && Arrays.binarySearch(broadcastAllowList, owningAppId) < 0) {
- registeredReceivers.remove(i);
- }
- }
- }
-
- int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
-
- // Merge into one list.
- int ir = 0;
- if (receivers != null) {
- // A special case for PACKAGE_ADDED: do not allow the package
- // being added to see this broadcast. This prevents them from
- // using this as a back door to get run as soon as they are
- // installed. Maybe in the future we want to have a special install
- // broadcast or such for apps, but we'd like to deliberately make
- // this decision.
- String skipPackages[] = null;
- if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())
- || Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())
- || Intent.ACTION_PACKAGE_DATA_CLEARED.equals(intent.getAction())) {
- Uri data = intent.getData();
- if (data != null) {
- String pkgName = data.getSchemeSpecificPart();
- if (pkgName != null) {
- skipPackages = new String[] { pkgName };
- }
- }
- } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {
- skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- }
- if (skipPackages != null && (skipPackages.length > 0)) {
- for (String skipPackage : skipPackages) {
- if (skipPackage != null) {
- int NT = receivers.size();
- for (int it=0; it<NT; it++) {
- ResolveInfo curt = (ResolveInfo)receivers.get(it);
- if (curt.activityInfo.packageName.equals(skipPackage)) {
- receivers.remove(it);
- it--;
- NT--;
- }
- }
- }
- }
- }
-
- int NT = receivers != null ? receivers.size() : 0;
- int it = 0;
- ResolveInfo curt = null;
- BroadcastFilter curr = null;
- while (it < NT && ir < NR) {
- if (curt == null) {
- curt = (ResolveInfo)receivers.get(it);
- }
- if (curr == null) {
- curr = registeredReceivers.get(ir);
- }
- if (curr.getPriority() >= curt.priority) {
- // Insert this broadcast record into the final list.
- receivers.add(it, curr);
- ir++;
- curr = null;
- it++;
- NT++;
- } else {
- // Skip to the next ResolveInfo in the final list.
- it++;
- curt = null;
- }
- }
- }
- while (ir < NR) {
- if (receivers == null) {
- receivers = new ArrayList();
- }
- receivers.add(registeredReceivers.get(ir));
- ir++;
- }
-
- if (isCallerSystem) {
- checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
- isProtectedBroadcast, receivers);
- }
-
- if ((receivers != null && receivers.size() > 0)
- || resultTo != null) {
- BroadcastQueue queue = mBroadcastQueue;
- SaferIntentUtils.filterNonExportedComponents(args, receivers);
- BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
- callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
- requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
- receivers, resultToApp, resultTo, resultCode, resultData, resultExtras,
- ordered, sticky, false, userId,
- backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
- callerAppProcessState);
- broadcastSentEventRecord.setBroadcastRecord(r);
-
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
- queue.enqueueBroadcastLocked(r);
- } else {
- // There was nobody interested in the broadcast, but we still want to record
- // that it happened.
- if (intent.getComponent() == null && intent.getPackage() == null
- && (intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
- // This was an implicit broadcast... let's record it for posterity.
- addBroadcastStatLocked(intent.getAction(), callerPackage, 0, 0, 0);
- }
- }
-
- return ActivityManager.BROADCAST_SUCCESS;
- }
-
- @GuardedBy("this")
- private void scheduleCanceledResultTo(ProcessRecord resultToApp, IIntentReceiver resultTo,
- Intent intent, int userId, BroadcastOptions options, int callingUid,
- String callingPackage) {
- if (resultTo == null) {
- return;
- }
- final ProcessRecord app = resultToApp;
- final IApplicationThread thread = (app != null) ? app.getOnewayThread() : null;
- if (thread != null) {
- try {
- final boolean shareIdentity = (options != null && options.isShareIdentityEnabled());
- thread.scheduleRegisteredReceiver(
- resultTo, intent, Activity.RESULT_CANCELED, null, null,
- false, false, true, userId, app.mState.getReportedProcState(),
- shareIdentity ? callingUid : Process.INVALID_UID,
- shareIdentity ? callingPackage : null);
- } catch (RemoteException e) {
- final String msg = "Failed to schedule result of " + intent + " via "
- + app + ": " + e;
- app.killLocked("Can't schedule resultTo", ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
- Slog.d(TAG, msg);
- }
- }
- }
-
- @GuardedBy("this")
- private int getRealProcessStateLocked(ProcessRecord app, int pid) {
- if (app == null) {
- synchronized (mPidsSelfLocked) {
- app = mPidsSelfLocked.get(pid);
- }
- }
- if (app != null && app.getThread() != null && !app.isKilled()) {
- return app.mState.getCurProcState();
- }
- return PROCESS_STATE_NONEXISTENT;
- }
-
- @GuardedBy("this")
- private int getRealUidStateLocked(ProcessRecord app, int pid) {
- if (app == null) {
- synchronized (mPidsSelfLocked) {
- app = mPidsSelfLocked.get(pid);
- }
- }
- if (app != null && app.getThread() != null && !app.isKilled()) {
- final UidRecord uidRecord = app.getUidRecord();
- if (uidRecord != null) {
- return uidRecord.getCurProcState();
- }
- }
- return PROCESS_STATE_NONEXISTENT;
- }
-
- @VisibleForTesting
- ArrayList<StickyBroadcast> getStickyBroadcastsForTest(String action, int userId) {
- synchronized (mStickyBroadcasts) {
- final ArrayMap<String, ArrayList<StickyBroadcast>> stickyBroadcasts =
- mStickyBroadcasts.get(userId);
- if (stickyBroadcasts == null) {
- return null;
- }
- return stickyBroadcasts.get(action);
- }
- }
-
- /**
- * @return uid from the extra field {@link Intent#EXTRA_UID} if present, Otherwise -1
- */
- private int getUidFromIntent(Intent intent) {
- if (intent == null) {
- return -1;
- }
- final Bundle intentExtras = intent.getExtras();
- return intent.hasExtra(Intent.EXTRA_UID)
- ? intentExtras.getInt(Intent.EXTRA_UID) : -1;
- }
-
- final void rotateBroadcastStatsIfNeededLocked() {
- final long now = SystemClock.elapsedRealtime();
- if (mCurBroadcastStats == null ||
- (mCurBroadcastStats.mStartRealtime +(24*60*60*1000) < now)) {
- mLastBroadcastStats = mCurBroadcastStats;
- if (mLastBroadcastStats != null) {
- mLastBroadcastStats.mEndRealtime = SystemClock.elapsedRealtime();
- mLastBroadcastStats.mEndUptime = SystemClock.uptimeMillis();
- }
- mCurBroadcastStats = new BroadcastStats();
- }
- }
-
- final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
- int skipCount, long dispatchTime) {
- rotateBroadcastStatsIfNeededLocked();
- mCurBroadcastStats.addBroadcast(action, srcPackage, receiveCount, skipCount, dispatchTime);
- }
-
- final void addBackgroundCheckViolationLocked(String action, String targetPackage) {
- rotateBroadcastStatsIfNeededLocked();
- mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage);
- }
-
- final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
- final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
- final String callerPackage = info != null ? info.packageName : original.callerPackage;
- if (callerPackage != null) {
- mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
- original.callingUid, 0, callerPackage).sendToTarget();
- }
- }
-
- final Intent verifyBroadcastLocked(Intent intent) {
- if (intent != null) {
- intent.prepareToEnterSystemServer();
- }
-
- int flags = intent.getFlags();
-
- if (!mProcessesReady) {
- // if the caller really truly claims to know what they're doing, go
- // ahead and allow the broadcast without launching any receivers
- if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) != 0) {
- // This will be turned into a FLAG_RECEIVER_REGISTERED_ONLY later on if needed.
- } else if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
- Slog.e(TAG, "Attempt to launch receivers of broadcast intent " + intent
- + " before boot completion");
- throw new IllegalStateException("Cannot broadcast before boot completed");
- }
- }
-
- if ((flags&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
- throw new IllegalArgumentException(
- "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
- }
-
- if ((flags & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
- switch (Binder.getCallingUid()) {
- case ROOT_UID:
- case SHELL_UID:
- break;
- default:
- Slog.w(TAG, "Removing FLAG_RECEIVER_FROM_SHELL because caller is UID "
- + Binder.getCallingUid());
- intent.removeFlags(Intent.FLAG_RECEIVER_FROM_SHELL);
- break;
- }
- }
-
- return intent;
- }
-
/**
* @deprecated Use {@link #broadcastIntentWithFeature}
*/
@@ -16344,110 +14260,14 @@
String[] requiredPermissions, String[] excludedPermissions,
String[] excludedPackages, int appOp, Bundle bOptions,
boolean serialized, boolean sticky, int userId) {
- enforceNotIsolatedCaller("broadcastIntent");
-
- synchronized(this) {
- intent = verifyBroadcastLocked(intent);
-
- final ProcessRecord callerApp = getRecordForAppLOSP(caller);
- final int callingPid = Binder.getCallingPid();
- final int callingUid = Binder.getCallingUid();
-
- // We're delivering the result to the caller
- final ProcessRecord resultToApp = callerApp;
-
- // Permission regimes around sender-supplied broadcast options.
- enforceBroadcastOptionPermissionsInternal(bOptions, callingUid);
-
- final ComponentName cn = intent.getComponent();
-
- Trace.traceBegin(
- Trace.TRACE_TAG_ACTIVITY_MANAGER,
- "broadcastIntent:" + (cn != null ? cn.toString() : intent.getAction()));
-
- final long origId = Binder.clearCallingIdentity();
- try {
- return broadcastIntentLocked(callerApp,
- callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
- intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
- resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
- appOp, bOptions, serialized, sticky, callingPid, callingUid, callingUid,
- callingPid, userId, BackgroundStartPrivileges.NONE, null, null);
- } finally {
- Binder.restoreCallingIdentity(origId);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- }
- }
- }
-
- // Not the binder call surface
- int broadcastIntentInPackage(String packageName, @Nullable String featureId, int uid,
- int realCallingUid, int realCallingPid, Intent intent, String resolvedType,
- ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode,
- String resultData, Bundle resultExtras, String requiredPermission, Bundle bOptions,
- boolean serialized, boolean sticky, int userId,
- BackgroundStartPrivileges backgroundStartPrivileges,
- @Nullable int[] broadcastAllowList) {
- synchronized(this) {
- intent = verifyBroadcastLocked(intent);
-
- final long origId = Binder.clearCallingIdentity();
- String[] requiredPermissions = requiredPermission == null ? null
- : new String[] {requiredPermission};
- try {
- return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
- resultToApp, resultTo, resultCode, resultData, resultExtras,
- requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
- uid, realCallingUid, realCallingPid, userId,
- backgroundStartPrivileges, broadcastAllowList,
- null /* filterExtrasForReceiver */);
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
+ return mBroadcastController.broadcastIntentWithFeature(caller, callingFeatureId, intent,
+ resolvedType, resultTo, resultCode, resultData, resultExtras, requiredPermissions,
+ excludedPermissions, excludedPackages, appOp, bOptions, serialized, sticky, userId);
}
@Override
public final void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) {
- // Refuse possible leaked file descriptors
- if (intent != null && intent.hasFileDescriptors() == true) {
- throw new IllegalArgumentException("File descriptors passed in Intent");
- }
-
- userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
- userId, true, ALLOW_NON_FULL, "removeStickyBroadcast", null);
-
- if (checkCallingPermission(android.Manifest.permission.BROADCAST_STICKY)
- != PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: unbroadcastIntent() from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
- + " requires " + android.Manifest.permission.BROADCAST_STICKY;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
- synchronized (mStickyBroadcasts) {
- ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
- if (stickies != null) {
- ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
- if (list != null) {
- int N = list.size();
- int i;
- for (i = 0; i < N; i++) {
- if (intent.filterEquals(list.get(i).intent)) {
- list.remove(i);
- break;
- }
- }
- if (list.size() <= 0) {
- stickies.remove(intent.getAction());
- }
- }
- if (stickies.size() <= 0) {
- mStickyBroadcasts.remove(userId);
- }
- }
- }
+ mBroadcastController.unbroadcastIntent(caller, intent, userId);
}
void backgroundServicesFinishedLocked(int userId) {
@@ -16456,31 +14276,32 @@
public void finishReceiver(IBinder caller, int resultCode, String resultData,
Bundle resultExtras, boolean resultAbort, int flags) {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Finish receiver: " + caller);
+ mBroadcastController.finishReceiver(caller, resultCode, resultData, resultExtras,
+ resultAbort, flags);
+ }
- // Refuse possible leaked file descriptors
- if (resultExtras != null && resultExtras.hasFileDescriptors()) {
- throw new IllegalArgumentException("File descriptors passed in Bundle");
- }
+ @VisibleForTesting
+ ArrayList<BroadcastController.StickyBroadcast> getStickyBroadcastsForTest(String action,
+ int userId) {
+ return mBroadcastController.getStickyBroadcastsForTest(action, userId);
+ }
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized(this) {
- final ProcessRecord callerApp = getRecordForAppLOSP(caller);
- if (callerApp == null) {
- Slog.w(TAG, "finishReceiver: no app for " + caller);
- return;
- }
+ final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
+ mBroadcastController.notifyBroadcastFinishedLocked(original);
+ }
- mBroadcastQueue.finishReceiverLocked(callerApp, resultCode,
- resultData, resultExtras, resultAbort, true);
- // updateOomAdjLocked() will be done here
- trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
- }
+ final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
+ int skipCount, long dispatchTime) {
+ mBroadcastController.addBroadcastStatLocked(action, srcPackage, receiveCount, skipCount,
+ dispatchTime);
+ }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
+ final void addBackgroundCheckViolationLocked(String action, String targetPackage) {
+ mBroadcastController.addBackgroundCheckViolationLocked(action, targetPackage);
+ }
+
+ void removeReceiverLocked(ReceiverList rl) {
+ mBroadcastController.removeReceiverLocked(rl);
}
// =========================================================
@@ -17845,7 +15666,7 @@
}
@GuardedBy("this")
- private void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
+ void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
// First remove any unused application processes whose package
// has been removed.
boolean didSomething = false;
@@ -18835,7 +16656,7 @@
@Override
public void enforceBroadcastOptionsPermissions(Bundle options, int callingUid) {
- enforceBroadcastOptionPermissionsInternal(options, callingUid);
+ mBroadcastController.enforceBroadcastOptionPermissionsInternal(options, callingUid);
}
/**
@@ -19225,7 +17046,7 @@
@Nullable int[] broadcastAllowList) {
synchronized (ActivityManagerService.this) {
final ProcessRecord resultToApp = getRecordForAppLOSP(resultToThread);
- return ActivityManagerService.this.broadcastIntentInPackage(packageName, featureId,
+ return mBroadcastController.broadcastIntentInPackage(packageName, featureId,
uid, realCallingUid, realCallingPid, intent, resolvedType, resultToApp,
resultTo, resultCode, resultData, resultExtras, requiredPermission,
bOptions, serialized, sticky, userId,
@@ -19242,13 +17063,13 @@
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
@Nullable Bundle bOptions) {
synchronized (ActivityManagerService.this) {
- intent = verifyBroadcastLocked(intent);
+ intent = mBroadcastController.verifyBroadcastLocked(intent);
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
try {
- return ActivityManagerService.this.broadcastIntentLocked(null /*callerApp*/,
+ return mBroadcastController.broadcastIntentLocked(null /*callerApp*/,
null /*callerPackage*/, null /*callingFeatureId*/, intent,
null /* resolvedType */, null /* resultToApp */, resultTo,
0 /* resultCode */, null /* resultData */,
@@ -21165,26 +18986,6 @@
}
}
- /**
- * Gets an {@code int} argument from the given {@code index} on {@code args}, logging an error
- * message on {@code pw} when it cannot be parsed.
- *
- * Returns {@code int} argument or {@code invalidValue} if it could not be parsed.
- */
- private static int getIntArg(PrintWriter pw, String[] args, int index, int invalidValue) {
- if (index > args.length) {
- pw.println("Missing argument");
- return invalidValue;
- }
- String arg = args[index];
- try {
- return Integer.parseInt(arg);
- } catch (Exception e) {
- pw.printf("Non-numeric argument at index %d: %s\n", index, arg);
- return invalidValue;
- }
- }
-
private void notifyMediaProjectionEvent(int uid, @NonNull IBinder projectionToken,
@MediaProjectionTokenEvent int event) {
synchronized (mMediaProjectionTokenMap) {
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
new file mode 100644
index 0000000..32026b2
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -0,0 +1,2410 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST;
+import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
+import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.AppOpsManager.OP_NONE;
+import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+import static android.os.Process.BLUETOOTH_UID;
+import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.NETWORK_STACK_UID;
+import static android.os.Process.NFC_UID;
+import static android.os.Process.PHONE_UID;
+import static android.os.Process.ROOT_UID;
+import static android.os.Process.SE_UID;
+import static android.os.Process.SHELL_UID;
+import static android.os.Process.SYSTEM_UID;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
+import static com.android.server.am.ActivityManagerService.CLEAR_DNS_CACHE_MSG;
+import static com.android.server.am.ActivityManagerService.HANDLE_TRUST_STORAGE_UPDATE_MSG;
+import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
+import static com.android.server.am.ActivityManagerService.TAG;
+import static com.android.server.am.ActivityManagerService.UPDATE_HTTP_PROXY_MSG;
+import static com.android.server.am.ActivityManagerService.UPDATE_TIME_PREFERENCE_MSG;
+import static com.android.server.am.ActivityManagerService.UPDATE_TIME_ZONE;
+import static com.android.server.am.ActivityManagerService.checkComponentPermission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.ApplicationExitInfo;
+import android.app.ApplicationThreadConstants;
+import android.app.BackgroundStartPrivileges;
+import android.app.BroadcastOptions;
+import android.app.IApplicationThread;
+import android.app.compat.CompatChanges;
+import android.appwidget.AppWidgetManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.media.audiofx.AudioEffect;
+import android.net.ConnectivityManager;
+import android.net.Proxy;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.BinderProxy;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.text.style.SuggestionSpan;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.IntentResolver;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
+import com.android.server.pm.Computer;
+import com.android.server.pm.SaferIntentUtils;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
+import com.android.server.sdksandbox.SdkSandboxManagerLocal;
+import com.android.server.utils.Slogf;
+
+import dalvik.annotation.optimization.NeverCompile;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+class BroadcastController {
+ private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
+
+ /**
+ * It is now required for apps to explicitly set either
+ * {@link android.content.Context#RECEIVER_EXPORTED} or
+ * {@link android.content.Context#RECEIVER_NOT_EXPORTED} when registering a receiver for an
+ * unprotected broadcast in code.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ private static final long DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED = 161145287L;
+
+ // Maximum number of receivers an app can register.
+ private static final int MAX_RECEIVERS_ALLOWED_PER_APP = 1000;
+
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final ActivityManagerService mService;
+ @NonNull
+ private BroadcastQueue mBroadcastQueue;
+
+ @GuardedBy("mService")
+ BroadcastStats mLastBroadcastStats;
+
+ @GuardedBy("mService")
+ BroadcastStats mCurBroadcastStats;
+
+ /**
+ * Broadcast actions that will always be deliverable to unlaunched/background apps
+ */
+ @GuardedBy("mService")
+ private ArraySet<String> mBackgroundLaunchBroadcasts;
+
+ /**
+ * State of all active sticky broadcasts per user. Keys are the action of the
+ * sticky Intent, values are an ArrayList of all broadcasted intents with
+ * that action (which should usually be one). The SparseArray is keyed
+ * by the user ID the sticky is for, and can include UserHandle.USER_ALL
+ * for stickies that are sent to all users.
+ */
+ @GuardedBy("mStickyBroadcasts")
+ final SparseArray<ArrayMap<String, ArrayList<StickyBroadcast>>> mStickyBroadcasts =
+ new SparseArray<>();
+
+ /**
+ * Keeps track of all IIntentReceivers that have been registered for broadcasts.
+ * Hash keys are the receiver IBinder, hash value is a ReceiverList.
+ */
+ @GuardedBy("mService")
+ final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
+
+ /**
+ * Resolver for broadcast intents to registered receivers.
+ * Holds BroadcastFilter (subclass of IntentFilter).
+ */
+ final IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver =
+ new IntentResolver<>() {
+ @Override
+ protected boolean allowFilterResult(
+ BroadcastFilter filter, List<BroadcastFilter> dest) {
+ IBinder target = filter.receiverList.receiver.asBinder();
+ for (int i = dest.size() - 1; i >= 0; i--) {
+ if (dest.get(i).receiverList.receiver.asBinder() == target) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ protected BroadcastFilter newResult(@NonNull Computer computer, BroadcastFilter filter,
+ int match, int userId, long customFlags) {
+ if (userId == UserHandle.USER_ALL || filter.owningUserId == UserHandle.USER_ALL
+ || userId == filter.owningUserId) {
+ return super.newResult(computer, filter, match, userId, customFlags);
+ }
+ return null;
+ }
+
+ @Override
+ protected IntentFilter getIntentFilter(@NonNull BroadcastFilter input) {
+ return input;
+ }
+
+ @Override
+ protected BroadcastFilter[] newArray(int size) {
+ return new BroadcastFilter[size];
+ }
+
+ @Override
+ protected boolean isPackageForFilter(String packageName, BroadcastFilter filter) {
+ return packageName.equals(filter.packageName);
+ }
+ };
+
+ BroadcastController(Context context, ActivityManagerService service, BroadcastQueue queue) {
+ mContext = context;
+ mService = service;
+ mBroadcastQueue = queue;
+ }
+
+ void setBroadcastQueueForTest(BroadcastQueue broadcastQueue) {
+ mBroadcastQueue = broadcastQueue;
+ }
+
+ Intent registerReceiverWithFeature(IApplicationThread caller, String callerPackage,
+ String callerFeatureId, String receiverId, IIntentReceiver receiver,
+ IntentFilter filter, String permission, int userId, int flags) {
+ traceRegistrationBegin(receiverId, receiver, filter, userId);
+ try {
+ return registerReceiverWithFeatureTraced(caller, callerPackage, callerFeatureId,
+ receiverId, receiver, filter, permission, userId, flags);
+ } finally {
+ traceRegistrationEnd();
+ }
+ }
+
+ private static void traceRegistrationBegin(String receiverId, IIntentReceiver receiver,
+ IntentFilter filter, int userId) {
+ if (!Flags.traceReceiverRegistration()) {
+ return;
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ final StringBuilder sb = new StringBuilder("registerReceiver: ");
+ sb.append(Binder.getCallingUid()); sb.append('/');
+ sb.append(receiverId == null ? "null" : receiverId); sb.append('/');
+ final int actionsCount = filter.safeCountActions();
+ if (actionsCount > 0) {
+ for (int i = 0; i < actionsCount; ++i) {
+ sb.append(filter.getAction(i));
+ if (i != actionsCount - 1) sb.append(',');
+ }
+ } else {
+ sb.append("null");
+ }
+ sb.append('/');
+ sb.append('u'); sb.append(userId); sb.append('/');
+ sb.append(receiver == null ? "null" : receiver.asBinder());
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, sb.toString());
+ }
+ }
+
+ private static void traceRegistrationEnd() {
+ if (!Flags.traceReceiverRegistration()) {
+ return;
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ }
+
+ private Intent registerReceiverWithFeatureTraced(IApplicationThread caller,
+ String callerPackage, String callerFeatureId, String receiverId,
+ IIntentReceiver receiver, IntentFilter filter, String permission,
+ int userId, int flags) {
+ mService.enforceNotIsolatedCaller("registerReceiver");
+ ArrayList<StickyBroadcast> stickyBroadcasts = null;
+ ProcessRecord callerApp = null;
+ final boolean visibleToInstantApps =
+ (flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
+
+ int callingUid;
+ int callingPid;
+ boolean instantApp;
+ synchronized (mService.mProcLock) {
+ callerApp = mService.getRecordForAppLOSP(caller);
+ if (callerApp == null) {
+ Slog.w(TAG, "registerReceiverWithFeature: no app for " + caller);
+ return null;
+ }
+ if (callerApp.info.uid != SYSTEM_UID
+ && !callerApp.getPkgList().containsKey(callerPackage)
+ && !"android".equals(callerPackage)) {
+ throw new SecurityException("Given caller package " + callerPackage
+ + " is not running in process " + callerApp);
+ }
+ callingUid = callerApp.info.uid;
+ callingPid = callerApp.getPid();
+
+ instantApp = isInstantApp(callerApp, callerPackage, callingUid);
+ }
+ userId = mService.mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
+ ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
+
+ // Warn if system internals are registering for important broadcasts
+ // without also using a priority to ensure they process the event
+ // before normal apps hear about it
+ if (UserHandle.isCore(callingUid)) {
+ final int priority = filter.getPriority();
+ final boolean systemPriority = (priority >= IntentFilter.SYSTEM_HIGH_PRIORITY)
+ || (priority <= IntentFilter.SYSTEM_LOW_PRIORITY);
+ if (!systemPriority) {
+ final int N = filter.countActions();
+ for (int i = 0; i < N; i++) {
+ // TODO: expand to additional important broadcasts over time
+ final String action = filter.getAction(i);
+ if (action.startsWith("android.intent.action.USER_")
+ || action.startsWith("android.intent.action.PACKAGE_")
+ || action.startsWith("android.intent.action.UID_")
+ || action.startsWith("android.intent.action.EXTERNAL_")
+ || action.startsWith("android.bluetooth.")
+ || action.equals(Intent.ACTION_SHUTDOWN)) {
+ if (DEBUG_BROADCAST) {
+ Slog.wtf(TAG,
+ "System internals registering for " + filter.toLongString()
+ + " with app priority; this will race with apps!",
+ new Throwable());
+ }
+
+ // When undefined, assume that system internals need
+ // to hear about the event first; they can use
+ // SYSTEM_LOW_PRIORITY if they need to hear last
+ if (priority == 0) {
+ filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ Iterator<String> actions = filter.actionsIterator();
+ if (actions == null) {
+ ArrayList<String> noAction = new ArrayList<String>(1);
+ noAction.add(null);
+ actions = noAction.iterator();
+ }
+ boolean onlyProtectedBroadcasts = true;
+
+ // Collect stickies of users and check if broadcast is only registered for protected
+ // broadcasts
+ int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
+ synchronized (mStickyBroadcasts) {
+ while (actions.hasNext()) {
+ String action = actions.next();
+ for (int id : userIds) {
+ ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
+ mStickyBroadcasts.get(id);
+ if (stickies != null) {
+ ArrayList<StickyBroadcast> broadcasts = stickies.get(action);
+ if (broadcasts != null) {
+ if (stickyBroadcasts == null) {
+ stickyBroadcasts = new ArrayList<>();
+ }
+ stickyBroadcasts.addAll(broadcasts);
+ }
+ }
+ }
+ if (onlyProtectedBroadcasts) {
+ try {
+ onlyProtectedBroadcasts &=
+ AppGlobals.getPackageManager().isProtectedBroadcast(action);
+ } catch (RemoteException e) {
+ onlyProtectedBroadcasts = false;
+ Slog.w(TAG, "Remote exception", e);
+ }
+ }
+ }
+ }
+
+ if (Process.isSdkSandboxUid(Binder.getCallingUid())) {
+ SdkSandboxManagerLocal sdkSandboxManagerLocal =
+ LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
+ if (sdkSandboxManagerLocal == null) {
+ throw new IllegalStateException("SdkSandboxManagerLocal not found when checking"
+ + " whether SDK sandbox uid can register to broadcast receivers.");
+ }
+ if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver(
+ /*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) {
+ throw new SecurityException("SDK sandbox not allowed to register receiver"
+ + " with the given IntentFilter: " + filter.toLongString());
+ }
+ }
+
+ // If the change is enabled, but neither exported or not exported is set, we need to log
+ // an error so the consumer can know to explicitly set the value for their flag.
+ // If the caller is registering for a sticky broadcast with a null receiver, we won't
+ // require a flag
+ final boolean explicitExportStateDefined =
+ (flags & (Context.RECEIVER_EXPORTED | Context.RECEIVER_NOT_EXPORTED)) != 0;
+ if (((flags & Context.RECEIVER_EXPORTED) != 0) && (
+ (flags & Context.RECEIVER_NOT_EXPORTED) != 0)) {
+ throw new IllegalArgumentException(
+ "Receiver can't specify both RECEIVER_EXPORTED and RECEIVER_NOT_EXPORTED"
+ + "flag");
+ }
+
+ // Don't enforce the flag check if we're EITHER registering for only protected
+ // broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
+ // not be used generally, so we will be marking them as exported by default
+ boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
+ DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid);
+
+ // A receiver that is visible to instant apps must also be exported.
+ final boolean unexportedReceiverVisibleToInstantApps =
+ ((flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0) && (
+ (flags & Context.RECEIVER_NOT_EXPORTED) != 0);
+ if (unexportedReceiverVisibleToInstantApps && requireExplicitFlagForDynamicReceivers) {
+ throw new IllegalArgumentException(
+ "Receiver can't specify both RECEIVER_VISIBLE_TO_INSTANT_APPS and "
+ + "RECEIVER_NOT_EXPORTED flag");
+ }
+
+ if (!onlyProtectedBroadcasts) {
+ if (receiver == null && !explicitExportStateDefined) {
+ // sticky broadcast, no flag specified (flag isn't required)
+ flags |= Context.RECEIVER_EXPORTED;
+ } else if (requireExplicitFlagForDynamicReceivers && !explicitExportStateDefined) {
+ throw new SecurityException(
+ callerPackage + ": One of RECEIVER_EXPORTED or "
+ + "RECEIVER_NOT_EXPORTED should be specified when a receiver "
+ + "isn't being registered exclusively for system broadcasts");
+ // Assume default behavior-- flag check is not enforced
+ } else if (!requireExplicitFlagForDynamicReceivers && (
+ (flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
+ // Change is not enabled, assume exported unless otherwise specified.
+ flags |= Context.RECEIVER_EXPORTED;
+ }
+ } else if ((flags & Context.RECEIVER_NOT_EXPORTED) == 0) {
+ flags |= Context.RECEIVER_EXPORTED;
+ }
+
+ // Dynamic receivers are exported by default for versions prior to T
+ final boolean exported = (flags & Context.RECEIVER_EXPORTED) != 0;
+
+ ArrayList<StickyBroadcast> allSticky = null;
+ if (stickyBroadcasts != null) {
+ final ContentResolver resolver = mContext.getContentResolver();
+ // Look for any matching sticky broadcasts...
+ for (int i = 0, N = stickyBroadcasts.size(); i < N; i++) {
+ final StickyBroadcast broadcast = stickyBroadcasts.get(i);
+ Intent intent = broadcast.intent;
+ // Don't provided intents that aren't available to instant apps.
+ if (instantApp && (intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS)
+ == 0) {
+ continue;
+ }
+ // If intent has scheme "content", it will need to access
+ // provider that needs to lock mProviderMap in ActivityThread
+ // and also it may need to wait application response, so we
+ // cannot lock ActivityManagerService here.
+ final int match;
+ if (Flags.avoidResolvingType()) {
+ match = filter.match(intent.getAction(), broadcast.resolvedDataType,
+ intent.getScheme(), intent.getData(), intent.getCategories(),
+ TAG, false /* supportsWildcards */, null /* ignoreActions */,
+ intent.getExtras());
+ } else {
+ match = filter.match(resolver, intent, true, TAG);
+ }
+ if (match >= 0) {
+ if (allSticky == null) {
+ allSticky = new ArrayList<>();
+ }
+ allSticky.add(broadcast);
+ }
+ }
+ }
+
+ // The first sticky in the list is returned directly back to the client.
+ Intent sticky = allSticky != null ? allSticky.get(0).intent : null;
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);
+ if (receiver == null) {
+ return sticky;
+ }
+
+ // SafetyNet logging for b/177931370. If any process other than system_server tries to
+ // listen to this broadcast action, then log it.
+ if (callingPid != Process.myPid()) {
+ if (filter.hasAction("com.android.server.net.action.SNOOZE_WARNING")
+ || filter.hasAction("com.android.server.net.action.SNOOZE_RAPID")) {
+ EventLog.writeEvent(0x534e4554, "177931370", callingUid, "");
+ }
+ }
+
+ synchronized (mService) {
+ IApplicationThread thread;
+ if (callerApp != null && ((thread = callerApp.getThread()) == null
+ || thread.asBinder() != caller.asBinder())) {
+ // Original caller already died
+ return null;
+ }
+ ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
+ if (rl == null) {
+ rl = new ReceiverList(mService, callerApp, callingPid, callingUid,
+ userId, receiver);
+ if (rl.app != null) {
+ final int totalReceiversForApp = rl.app.mReceivers.numberOfReceivers();
+ if (totalReceiversForApp >= MAX_RECEIVERS_ALLOWED_PER_APP) {
+ throw new IllegalStateException("Too many receivers, total of "
+ + totalReceiversForApp + ", registered for pid: "
+ + rl.pid + ", callerPackage: " + callerPackage);
+ }
+ rl.app.mReceivers.addReceiver(rl);
+ } else {
+ try {
+ receiver.asBinder().linkToDeath(rl, 0);
+ } catch (RemoteException e) {
+ return sticky;
+ }
+ rl.linkedToDeath = true;
+ }
+ mRegisteredReceivers.put(receiver.asBinder(), rl);
+ } else if (rl.uid != callingUid) {
+ throw new IllegalArgumentException(
+ "Receiver requested to register for uid " + callingUid
+ + " was previously registered for uid " + rl.uid
+ + " callerPackage is " + callerPackage);
+ } else if (rl.pid != callingPid) {
+ throw new IllegalArgumentException(
+ "Receiver requested to register for pid " + callingPid
+ + " was previously registered for pid " + rl.pid
+ + " callerPackage is " + callerPackage);
+ } else if (rl.userId != userId) {
+ throw new IllegalArgumentException(
+ "Receiver requested to register for user " + userId
+ + " was previously registered for user " + rl.userId
+ + " callerPackage is " + callerPackage);
+ }
+ BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
+ receiverId, permission, callingUid, userId, instantApp, visibleToInstantApps,
+ exported);
+ if (rl.containsFilter(filter)) {
+ Slog.w(TAG, "Receiver with filter " + filter
+ + " already registered for pid " + rl.pid
+ + ", callerPackage is " + callerPackage);
+ } else {
+ rl.add(bf);
+ if (!bf.debugCheck()) {
+ Slog.w(TAG, "==> For Dynamic broadcast");
+ }
+ mReceiverResolver.addFilter(mService.getPackageManagerInternal().snapshot(), bf);
+ }
+
+ // Enqueue broadcasts for all existing stickies that match
+ // this filter.
+ if (allSticky != null) {
+ ArrayList receivers = new ArrayList();
+ receivers.add(bf);
+ sticky = null;
+
+ final int stickyCount = allSticky.size();
+ for (int i = 0; i < stickyCount; i++) {
+ final StickyBroadcast broadcast = allSticky.get(i);
+ final int originalStickyCallingUid = allSticky.get(i).originalCallingUid;
+ // TODO(b/281889567): consider using checkComponentPermission instead of
+ // canAccessUnexportedComponents
+ if (sticky == null && (exported || originalStickyCallingUid == callingUid
+ || ActivityManager.canAccessUnexportedComponents(
+ originalStickyCallingUid))) {
+ sticky = broadcast.intent;
+ }
+ BroadcastQueue queue = mBroadcastQueue;
+ BroadcastRecord r = new BroadcastRecord(queue, broadcast.intent, null,
+ null, null, -1, -1, false, null, null, null, null, OP_NONE,
+ BroadcastOptions.makeWithDeferUntilActive(broadcast.deferUntilActive),
+ receivers, null, null, 0, null, null, false, true, true, -1,
+ originalStickyCallingUid, BackgroundStartPrivileges.NONE,
+ false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
+ null /* filterExtrasForReceiver */,
+ broadcast.originalCallingAppProcessState);
+ queue.enqueueBroadcastLocked(r);
+ }
+ }
+
+ return sticky;
+ }
+ }
+
+ void unregisterReceiver(IIntentReceiver receiver) {
+ traceUnregistrationBegin(receiver);
+ try {
+ unregisterReceiverTraced(receiver);
+ } finally {
+ traceUnregistrationEnd();
+ }
+ }
+
+ private static void traceUnregistrationBegin(IIntentReceiver receiver) {
+ if (!Flags.traceReceiverRegistration()) {
+ return;
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ TextUtils.formatSimple("unregisterReceiver: %d/%s", Binder.getCallingUid(),
+ receiver == null ? "null" : receiver.asBinder()));
+ }
+ }
+
+ private static void traceUnregistrationEnd() {
+ if (!Flags.traceReceiverRegistration()) {
+ return;
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ }
+
+ private void unregisterReceiverTraced(IIntentReceiver receiver) {
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Unregister receiver: " + receiver);
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ boolean doTrim = false;
+ synchronized (mService) {
+ ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
+ if (rl != null) {
+ final BroadcastRecord r = rl.curBroadcast;
+ if (r != null) {
+ final boolean doNext = r.queue.finishReceiverLocked(
+ rl.app, r.resultCode, r.resultData, r.resultExtras,
+ r.resultAbort, false);
+ if (doNext) {
+ doTrim = true;
+ }
+ }
+ if (rl.app != null) {
+ rl.app.mReceivers.removeReceiver(rl);
+ }
+ removeReceiverLocked(rl);
+ if (rl.linkedToDeath) {
+ rl.linkedToDeath = false;
+ rl.receiver.asBinder().unlinkToDeath(rl, 0);
+ }
+ }
+
+ // If we actually concluded any broadcasts, we might now be able
+ // to trim the recipients' apps from our working set
+ if (doTrim) {
+ mService.trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
+ return;
+ }
+ }
+
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ void removeReceiverLocked(ReceiverList rl) {
+ mRegisteredReceivers.remove(rl.receiver.asBinder());
+ for (int i = rl.size() - 1; i >= 0; i--) {
+ mReceiverResolver.removeFilter(rl.get(i));
+ }
+ }
+
+ int broadcastIntentWithFeature(IApplicationThread caller, String callingFeatureId,
+ Intent intent, String resolvedType, IIntentReceiver resultTo,
+ int resultCode, String resultData, Bundle resultExtras,
+ String[] requiredPermissions, String[] excludedPermissions,
+ String[] excludedPackages, int appOp, Bundle bOptions,
+ boolean serialized, boolean sticky, int userId) {
+ mService.enforceNotIsolatedCaller("broadcastIntent");
+
+ synchronized (mService) {
+ intent = verifyBroadcastLocked(intent);
+
+ final ProcessRecord callerApp = mService.getRecordForAppLOSP(caller);
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+
+ // We're delivering the result to the caller
+ final ProcessRecord resultToApp = callerApp;
+
+ // Permission regimes around sender-supplied broadcast options.
+ enforceBroadcastOptionPermissionsInternal(bOptions, callingUid);
+
+ final ComponentName cn = intent.getComponent();
+
+ Trace.traceBegin(
+ Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "broadcastIntent:" + (cn != null ? cn.toString() : intent.getAction()));
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ return broadcastIntentLocked(callerApp,
+ callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
+ intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
+ resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
+ appOp, bOptions, serialized, sticky, callingPid, callingUid, callingUid,
+ callingPid, userId, BackgroundStartPrivileges.NONE, null, null);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ }
+ }
+
+ // Not the binder call surface
+ int broadcastIntentInPackage(String packageName, @Nullable String featureId, int uid,
+ int realCallingUid, int realCallingPid, Intent intent, String resolvedType,
+ ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode,
+ String resultData, Bundle resultExtras, String requiredPermission, Bundle bOptions,
+ boolean serialized, boolean sticky, int userId,
+ BackgroundStartPrivileges backgroundStartPrivileges,
+ @Nullable int[] broadcastAllowList) {
+ synchronized (mService) {
+ intent = verifyBroadcastLocked(intent);
+
+ final long origId = Binder.clearCallingIdentity();
+ String[] requiredPermissions = requiredPermission == null ? null
+ : new String[] {requiredPermission};
+ try {
+ return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
+ resultToApp, resultTo, resultCode, resultData, resultExtras,
+ requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
+ uid, realCallingUid, realCallingPid, userId,
+ backgroundStartPrivileges, broadcastAllowList,
+ null /* filterExtrasForReceiver */);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+
+ @GuardedBy("mService")
+ final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage,
+ @Nullable String callerFeatureId, Intent intent, String resolvedType,
+ ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
+ Bundle resultExtras, String[] requiredPermissions,
+ String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
+ boolean ordered, boolean sticky, int callingPid, int callingUid,
+ int realCallingUid, int realCallingPid, int userId,
+ BackgroundStartPrivileges backgroundStartPrivileges,
+ @Nullable int[] broadcastAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
+ final int cookie = traceBroadcastIntentBegin(intent, resultTo, ordered, sticky,
+ callingUid, realCallingUid, userId);
+ try {
+ final BroadcastSentEventRecord broadcastSentEventRecord =
+ new BroadcastSentEventRecord();
+ final int res = broadcastIntentLockedTraced(callerApp, callerPackage, callerFeatureId,
+ intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
+ resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
+ appOp, BroadcastOptions.fromBundleNullable(bOptions), ordered, sticky,
+ callingPid, callingUid, realCallingUid, realCallingPid, userId,
+ backgroundStartPrivileges, broadcastAllowList, filterExtrasForReceiver,
+ broadcastSentEventRecord);
+ broadcastSentEventRecord.setResult(res);
+ broadcastSentEventRecord.logToStatsd();
+ return res;
+ } finally {
+ traceBroadcastIntentEnd(cookie);
+ }
+ }
+
+ private static int traceBroadcastIntentBegin(Intent intent, IIntentReceiver resultTo,
+ boolean ordered, boolean sticky, int callingUid, int realCallingUid, int userId) {
+ if (!Flags.traceReceiverRegistration()) {
+ return BroadcastQueue.traceBegin("broadcastIntentLockedTraced");
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ final StringBuilder sb = new StringBuilder("broadcastIntent: ");
+ sb.append(callingUid); sb.append('/');
+ final String action = intent.getAction();
+ sb.append(action == null ? null : action); sb.append('/');
+ sb.append("0x"); sb.append(Integer.toHexString(intent.getFlags())); sb.append('/');
+ sb.append(ordered ? "O" : "_");
+ sb.append(sticky ? "S" : "_");
+ sb.append(resultTo != null ? "C" : "_");
+ sb.append('/');
+ sb.append('u'); sb.append(userId);
+ if (callingUid != realCallingUid) {
+ sb.append('/');
+ sb.append("sender="); sb.append(realCallingUid);
+ }
+ return BroadcastQueue.traceBegin(sb.toString());
+ }
+ return 0;
+ }
+
+ private static void traceBroadcastIntentEnd(int cookie) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ BroadcastQueue.traceEnd(cookie);
+ }
+ }
+
+ @GuardedBy("mService")
+ final int broadcastIntentLockedTraced(ProcessRecord callerApp, String callerPackage,
+ @Nullable String callerFeatureId, Intent intent, String resolvedType,
+ ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
+ Bundle resultExtras, String[] requiredPermissions,
+ String[] excludedPermissions, String[] excludedPackages, int appOp,
+ BroadcastOptions brOptions, boolean ordered, boolean sticky, int callingPid,
+ int callingUid, int realCallingUid, int realCallingPid, int userId,
+ BackgroundStartPrivileges backgroundStartPrivileges,
+ @Nullable int[] broadcastAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+ @NonNull BroadcastSentEventRecord broadcastSentEventRecord) {
+ // Ensure all internal loopers are registered for idle checks
+ BroadcastLoopers.addMyLooper();
+
+ if (Process.isSdkSandboxUid(realCallingUid)) {
+ final SdkSandboxManagerLocal sdkSandboxManagerLocal = LocalManagerRegistry.getManager(
+ SdkSandboxManagerLocal.class);
+ if (sdkSandboxManagerLocal == null) {
+ throw new IllegalStateException("SdkSandboxManagerLocal not found when sending"
+ + " a broadcast from an SDK sandbox uid.");
+ }
+ if (!sdkSandboxManagerLocal.canSendBroadcast(intent)) {
+ throw new SecurityException(
+ "Intent " + intent.getAction() + " may not be broadcast from an SDK sandbox"
+ + " uid. Given caller package " + callerPackage
+ + " (pid=" + callingPid + ", realCallingUid=" + realCallingUid
+ + ", callingUid= " + callingUid + ")");
+ }
+ }
+
+ if ((resultTo != null) && (resultToApp == null)) {
+ if (resultTo.asBinder() instanceof BinderProxy) {
+ // Warn when requesting results without a way to deliver them
+ Slog.wtf(TAG, "Sending broadcast " + intent.getAction()
+ + " with resultTo requires resultToApp", new Throwable());
+ } else {
+ // If not a BinderProxy above, then resultTo is an in-process
+ // receiver, so splice in system_server process
+ resultToApp = mService.getProcessRecordLocked("system", SYSTEM_UID);
+ }
+ }
+
+ intent = new Intent(intent);
+ broadcastSentEventRecord.setIntent(intent);
+ broadcastSentEventRecord.setOriginalIntentFlags(intent.getFlags());
+ broadcastSentEventRecord.setSenderUid(callingUid);
+ broadcastSentEventRecord.setRealSenderUid(realCallingUid);
+ broadcastSentEventRecord.setSticky(sticky);
+ broadcastSentEventRecord.setOrdered(ordered);
+ broadcastSentEventRecord.setResultRequested(resultTo != null);
+ final int callerAppProcessState = getRealProcessStateLocked(callerApp, realCallingPid);
+ broadcastSentEventRecord.setSenderProcState(callerAppProcessState);
+ broadcastSentEventRecord.setSenderUidState(getRealUidStateLocked(callerApp,
+ realCallingPid));
+
+ final boolean callerInstantApp = isInstantApp(callerApp, callerPackage, callingUid);
+ // Instant Apps cannot use FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS
+ if (callerInstantApp) {
+ intent.setFlags(intent.getFlags() & ~Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
+ }
+
+ if (userId == UserHandle.USER_ALL && broadcastAllowList != null) {
+ Slog.e(TAG, "broadcastAllowList only applies when sending to individual users. "
+ + "Assuming restrictive whitelist.");
+ broadcastAllowList = new int[]{};
+ }
+
+ // By default broadcasts do not go to stopped apps.
+ intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
+
+ // If we have not finished booting, don't allow this to launch new processes.
+ if (!mService.mProcessesReady
+ && (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ }
+
+ if (DEBUG_BROADCAST_LIGHT) {
+ Slog.v(TAG_BROADCAST,
+ (sticky ? "Broadcast sticky: " : "Broadcast: ") + intent
+ + " ordered=" + ordered + " userid=" + userId
+ + " options=" + (brOptions == null ? "null" : brOptions.toBundle()));
+ }
+ if ((resultTo != null) && !ordered) {
+ if (!UserHandle.isCore(callingUid)) {
+ String msg = "Unauthorized unordered resultTo broadcast "
+ + intent + " sent from uid " + callingUid;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ }
+
+ userId = mService.mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
+ ALLOW_NON_FULL, "broadcast", callerPackage);
+
+ // Make sure that the user who is receiving this broadcast or its parent is running.
+ // If not, we will just skip it. Make an exception for shutdown broadcasts, upgrade steps.
+ if (userId != UserHandle.USER_ALL && !mService.mUserController.isUserOrItsParentRunning(
+ userId)) {
+ if ((callingUid != SYSTEM_UID
+ || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
+ && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
+ Slog.w(TAG, "Skipping broadcast of " + intent
+ + ": user " + userId + " and its parent (if any) are stopped");
+ scheduleCanceledResultTo(resultToApp, resultTo, intent, userId,
+ brOptions, callingUid, callerPackage);
+ return ActivityManager.BROADCAST_FAILED_USER_STOPPED;
+ }
+ }
+
+ final String action = intent.getAction();
+ if (brOptions != null) {
+ if (brOptions.getTemporaryAppAllowlistDuration() > 0) {
+ // See if the caller is allowed to do this. Note we are checking against
+ // the actual real caller (not whoever provided the operation as say a
+ // PendingIntent), because that who is actually supplied the arguments.
+ if (checkComponentPermission(CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
+ realCallingPid, realCallingUid, -1, true)
+ != PackageManager.PERMISSION_GRANTED
+ && checkComponentPermission(START_ACTIVITIES_FROM_BACKGROUND,
+ realCallingPid, realCallingUid, -1, true)
+ != PackageManager.PERMISSION_GRANTED
+ && checkComponentPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND,
+ realCallingPid, realCallingUid, -1, true)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: " + intent.getAction()
+ + " broadcast from " + callerPackage + " (pid=" + callingPid
+ + ", uid=" + callingUid + ")"
+ + " requires "
+ + CHANGE_DEVICE_IDLE_TEMP_WHITELIST + " or "
+ + START_ACTIVITIES_FROM_BACKGROUND + " or "
+ + START_FOREGROUND_SERVICES_FROM_BACKGROUND;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ }
+ if (brOptions.isDontSendToRestrictedApps()
+ && !mService.isUidActiveLOSP(callingUid)
+ && mService.isBackgroundRestrictedNoCheck(callingUid, callerPackage)) {
+ Slog.i(TAG, "Not sending broadcast " + action + " - app " + callerPackage
+ + " has background restrictions");
+ return ActivityManager.START_CANCELED;
+ }
+ if (brOptions.allowsBackgroundActivityStarts()) {
+ // See if the caller is allowed to do this. Note we are checking against
+ // the actual real caller (not whoever provided the operation as say a
+ // PendingIntent), because that who is actually supplied the arguments.
+ if (checkComponentPermission(
+ android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
+ realCallingPid, realCallingUid, -1, true)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: " + intent.getAction()
+ + " broadcast from " + callerPackage + " (pid=" + callingPid
+ + ", uid=" + callingUid + ")"
+ + " requires "
+ + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ } else {
+ // We set the token to null since if it wasn't for it we'd allow anyway here
+ backgroundStartPrivileges = BackgroundStartPrivileges.ALLOW_BAL;
+ }
+ }
+
+ if (brOptions.getIdForResponseEvent() > 0) {
+ mService.enforcePermission(
+ android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS,
+ callingPid, callingUid, "recordResponseEventWhileInBackground");
+ }
+ }
+
+ // Verify that protected broadcasts are only being sent by system code,
+ // and that system code is only sending protected broadcasts.
+ final boolean isProtectedBroadcast;
+ try {
+ isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Remote exception", e);
+ scheduleCanceledResultTo(resultToApp, resultTo, intent,
+ userId, brOptions, callingUid, callerPackage);
+ return ActivityManager.BROADCAST_SUCCESS;
+ }
+
+ final boolean isCallerSystem;
+ switch (UserHandle.getAppId(callingUid)) {
+ case ROOT_UID:
+ case SYSTEM_UID:
+ case PHONE_UID:
+ case BLUETOOTH_UID:
+ case NFC_UID:
+ case SE_UID:
+ case NETWORK_STACK_UID:
+ isCallerSystem = true;
+ break;
+ default:
+ isCallerSystem = (callerApp != null) && callerApp.isPersistent();
+ break;
+ }
+
+ // First line security check before anything else: stop non-system apps from
+ // sending protected broadcasts.
+ if (!isCallerSystem) {
+ if (isProtectedBroadcast) {
+ String msg = "Permission Denial: not allowed to send broadcast "
+ + action + " from pid="
+ + callingPid + ", uid=" + callingUid;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+
+ } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
+ || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
+ // Special case for compatibility: we don't want apps to send this,
+ // but historically it has not been protected and apps may be using it
+ // to poke their own app widget. So, instead of making it protected,
+ // just limit it to the caller.
+ if (callerPackage == null) {
+ String msg = "Permission Denial: not allowed to send broadcast "
+ + action + " from unknown caller.";
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ } else if (intent.getComponent() != null) {
+ // They are good enough to send to an explicit component... verify
+ // it is being sent to the calling app.
+ if (!intent.getComponent().getPackageName().equals(
+ callerPackage)) {
+ String msg = "Permission Denial: not allowed to send broadcast "
+ + action + " to "
+ + intent.getComponent().getPackageName() + " from "
+ + callerPackage;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ } else {
+ // Limit broadcast to their own package.
+ intent.setPackage(callerPackage);
+ }
+ }
+ }
+
+ boolean timeoutExempt = false;
+
+ if (action != null) {
+ if (getBackgroundLaunchBroadcasts().contains(action)) {
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "Broadcast action " + action + " forcing include-background");
+ }
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ }
+
+ // TODO: b/329211459 - Remove this after background remote intent is fixed.
+ if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+ && getWearRemoteIntentAction().equals(action)) {
+ final int callerProcState = callerApp != null
+ ? callerApp.getCurProcState()
+ : ActivityManager.PROCESS_STATE_NONEXISTENT;
+ if (ActivityManager.RunningAppProcessInfo.procStateToImportance(callerProcState)
+ > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+ return ActivityManager.START_CANCELED;
+ }
+ }
+
+ switch (action) {
+ case Intent.ACTION_MEDIA_SCANNER_SCAN_FILE:
+ UserManagerInternal umInternal = LocalServices.getService(
+ UserManagerInternal.class);
+ UserInfo userInfo = umInternal.getUserInfo(userId);
+ if (userInfo != null && userInfo.isCloneProfile()) {
+ userId = umInternal.getProfileParentId(userId);
+ }
+ break;
+ case Intent.ACTION_UID_REMOVED:
+ case Intent.ACTION_PACKAGE_REMOVED:
+ case Intent.ACTION_PACKAGE_CHANGED:
+ case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+ case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
+ case Intent.ACTION_PACKAGES_SUSPENDED:
+ case Intent.ACTION_PACKAGES_UNSUSPENDED:
+ // Handle special intents: if this broadcast is from the package
+ // manager about a package being removed, we need to remove all of
+ // its activities from the history stack.
+ if (checkComponentPermission(
+ android.Manifest.permission.BROADCAST_PACKAGE_REMOVED,
+ callingPid, callingUid, -1, true)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: " + intent.getAction()
+ + " broadcast from " + callerPackage + " (pid=" + callingPid
+ + ", uid=" + callingUid + ")"
+ + " requires "
+ + android.Manifest.permission.BROADCAST_PACKAGE_REMOVED;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ switch (action) {
+ case Intent.ACTION_UID_REMOVED:
+ final int uid = getUidFromIntent(intent);
+ if (uid >= 0) {
+ mService.mBatteryStatsService.removeUid(uid);
+ if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ mService.mAppOpsService.resetAllModes(UserHandle.getUserId(uid),
+ intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
+ } else {
+ mService.mAppOpsService.uidRemoved(uid);
+ mService.mServices.onUidRemovedLocked(uid);
+ }
+ }
+ break;
+ case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+ // If resources are unavailable just force stop all those packages
+ // and flush the attribute cache as well.
+ String[] list = intent.getStringArrayExtra(
+ Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ if (list != null && list.length > 0) {
+ for (int i = 0; i < list.length; i++) {
+ mService.forceStopPackageLocked(list[i], -1, false, true, true,
+ false, false, false, userId, "storage unmount");
+ }
+ mService.mAtmInternal.cleanupRecentTasksForUser(
+ UserHandle.USER_ALL);
+ sendPackageBroadcastLocked(
+ ApplicationThreadConstants.EXTERNAL_STORAGE_UNAVAILABLE,
+ list, userId);
+ }
+ break;
+ case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
+ mService.mAtmInternal.cleanupRecentTasksForUser(UserHandle.USER_ALL);
+ break;
+ case Intent.ACTION_PACKAGE_REMOVED:
+ case Intent.ACTION_PACKAGE_CHANGED:
+ Uri data = intent.getData();
+ String ssp;
+ if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+ boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals(action);
+ final boolean replacing =
+ intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+ final boolean killProcess =
+ !intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false);
+ final boolean fullUninstall = removed && !replacing;
+
+ if (removed) {
+ if (killProcess) {
+ mService.forceStopPackageLocked(ssp, UserHandle.getAppId(
+ intent.getIntExtra(Intent.EXTRA_UID, -1)),
+ false, true, true, false, fullUninstall, false,
+ userId, "pkg removed");
+ mService.getPackageManagerInternal()
+ .onPackageProcessKilledForUninstall(ssp);
+ } else {
+ // Kill any app zygotes always, since they can't fork new
+ // processes with references to the old code
+ mService.forceStopAppZygoteLocked(ssp, UserHandle.getAppId(
+ intent.getIntExtra(Intent.EXTRA_UID, -1)),
+ userId);
+ }
+ final int cmd = killProcess
+ ? ApplicationThreadConstants.PACKAGE_REMOVED
+ : ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL;
+ sendPackageBroadcastLocked(cmd,
+ new String[] {ssp}, userId);
+ if (fullUninstall) {
+ // Remove all permissions granted from/to this package
+ mService.mUgmInternal.removeUriPermissionsForPackage(ssp,
+ userId, true, false);
+
+ mService.mAtmInternal.removeRecentTasksByPackageName(ssp,
+ userId);
+
+ mService.mServices.forceStopPackageLocked(ssp, userId);
+ mService.mAtmInternal.onPackageUninstalled(ssp, userId);
+ mService.mBatteryStatsService.notePackageUninstalled(ssp);
+ }
+ } else {
+ if (killProcess) {
+ int reason;
+ int subReason;
+ if (replacing) {
+ reason = ApplicationExitInfo.REASON_PACKAGE_UPDATED;
+ subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
+ } else {
+ reason =
+ ApplicationExitInfo.REASON_PACKAGE_STATE_CHANGE;
+ subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
+ }
+
+ final int extraUid = intent.getIntExtra(Intent.EXTRA_UID,
+ -1);
+ synchronized (mService.mProcLock) {
+ mService.mProcessList.killPackageProcessesLSP(ssp,
+ UserHandle.getAppId(extraUid),
+ userId, ProcessList.INVALID_ADJ,
+ reason,
+ subReason,
+ "change " + ssp);
+ }
+ }
+ mService.cleanupDisabledPackageComponentsLocked(ssp, userId,
+ intent.getStringArrayExtra(
+ Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST));
+ mService.mServices.schedulePendingServiceStartLocked(
+ ssp, userId);
+ }
+ }
+ break;
+ case Intent.ACTION_PACKAGES_SUSPENDED:
+ case Intent.ACTION_PACKAGES_UNSUSPENDED:
+ final boolean suspended = Intent.ACTION_PACKAGES_SUSPENDED.equals(
+ intent.getAction());
+ final String[] packageNames = intent.getStringArrayExtra(
+ Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ final int userIdExtra = intent.getIntExtra(
+ Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+
+ mService.mAtmInternal.onPackagesSuspendedChanged(packageNames,
+ suspended, userIdExtra);
+
+ final boolean quarantined = intent.getBooleanExtra(
+ Intent.EXTRA_QUARANTINED, false);
+ if (suspended && quarantined && packageNames != null) {
+ for (int i = 0; i < packageNames.length; i++) {
+ mService.forceStopPackage(packageNames[i], userId,
+ ActivityManager.FLAG_OR_STOPPED, "quarantined");
+ }
+ }
+
+ break;
+ }
+ break;
+ case Intent.ACTION_PACKAGE_REPLACED: {
+ final Uri data = intent.getData();
+ final String ssp;
+ if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+ ApplicationInfo aInfo = null;
+ try {
+ aInfo = AppGlobals.getPackageManager()
+ .getApplicationInfo(ssp, STOCK_PM_FLAGS, userId);
+ } catch (RemoteException ignore) {
+ }
+ if (aInfo == null) {
+ Slog.w(TAG, "Dropping ACTION_PACKAGE_REPLACED for non-existent pkg:"
+ + " ssp=" + ssp + " data=" + data);
+ scheduleCanceledResultTo(resultToApp, resultTo, intent,
+ userId, brOptions, callingUid, callerPackage);
+ return ActivityManager.BROADCAST_SUCCESS;
+ }
+ mService.updateAssociationForApp(aInfo);
+ mService.mAtmInternal.onPackageReplaced(aInfo);
+ mService.mServices.updateServiceApplicationInfoLocked(aInfo);
+ sendPackageBroadcastLocked(ApplicationThreadConstants.PACKAGE_REPLACED,
+ new String[] {ssp}, userId);
+ }
+ break;
+ }
+ case Intent.ACTION_PACKAGE_ADDED: {
+ // Special case for adding a package: by default turn on compatibility mode.
+ Uri data = intent.getData();
+ String ssp;
+ if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+ final boolean replacing =
+ intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+ mService.mAtmInternal.onPackageAdded(ssp, replacing);
+
+ try {
+ ApplicationInfo ai = AppGlobals.getPackageManager()
+ .getApplicationInfo(ssp, STOCK_PM_FLAGS, 0);
+ mService.mBatteryStatsService.notePackageInstalled(ssp,
+ ai != null ? ai.longVersionCode : 0);
+ } catch (RemoteException e) {
+ }
+ }
+ break;
+ }
+ case Intent.ACTION_PACKAGE_DATA_CLEARED: {
+ Uri data = intent.getData();
+ String ssp;
+ if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+ mService.mAtmInternal.onPackageDataCleared(ssp, userId);
+ }
+ break;
+ }
+ case Intent.ACTION_TIMEZONE_CHANGED:
+ // If this is the time zone changed action, queue up a message that will reset
+ // the timezone of all currently running processes. This message will get
+ // queued up before the broadcast happens.
+ mService.mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
+ break;
+ case Intent.ACTION_TIME_CHANGED:
+ // EXTRA_TIME_PREF_24_HOUR_FORMAT is optional so we must distinguish between
+ // the tri-state value it may contain and "unknown".
+ // For convenience we re-use the Intent extra values.
+ final int NO_EXTRA_VALUE_FOUND = -1;
+ final int timeFormatPreferenceMsgValue = intent.getIntExtra(
+ Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT,
+ NO_EXTRA_VALUE_FOUND /* defaultValue */);
+ // Only send a message if the time preference is available.
+ if (timeFormatPreferenceMsgValue != NO_EXTRA_VALUE_FOUND) {
+ Message updateTimePreferenceMsg =
+ mService.mHandler.obtainMessage(UPDATE_TIME_PREFERENCE_MSG,
+ timeFormatPreferenceMsgValue, 0);
+ mService.mHandler.sendMessage(updateTimePreferenceMsg);
+ }
+ mService.mBatteryStatsService.noteCurrentTimeChanged();
+ break;
+ case ConnectivityManager.ACTION_CLEAR_DNS_CACHE:
+ mService.mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);
+ break;
+ case Proxy.PROXY_CHANGE_ACTION:
+ mService.mHandler.sendMessage(mService.mHandler.obtainMessage(
+ UPDATE_HTTP_PROXY_MSG));
+ break;
+ case android.hardware.Camera.ACTION_NEW_PICTURE:
+ case android.hardware.Camera.ACTION_NEW_VIDEO:
+ // In N we just turned these off; in O we are turing them back on partly,
+ // only for registered receivers. This will still address the main problem
+ // (a spam of apps waking up when a picture is taken putting significant
+ // memory pressure on the system at a bad point), while still allowing apps
+ // that are already actively running to know about this happening.
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ break;
+ case android.security.KeyChain.ACTION_TRUST_STORE_CHANGED:
+ mService.mHandler.sendEmptyMessage(HANDLE_TRUST_STORAGE_UPDATE_MSG);
+ break;
+ case "com.android.launcher.action.INSTALL_SHORTCUT":
+ // As of O, we no longer support this broadcasts, even for pre-O apps.
+ // Apps should now be using ShortcutManager.pinRequestShortcut().
+ Log.w(TAG, "Broadcast " + action
+ + " no longer supported. It will not be delivered.");
+ scheduleCanceledResultTo(resultToApp, resultTo, intent,
+ userId, brOptions, callingUid, callerPackage);
+ return ActivityManager.BROADCAST_SUCCESS;
+ case Intent.ACTION_PRE_BOOT_COMPLETED:
+ timeoutExempt = true;
+ break;
+ case Intent.ACTION_CLOSE_SYSTEM_DIALOGS:
+ if (!mService.mAtmInternal.checkCanCloseSystemDialogs(callingPid, callingUid,
+ callerPackage)) {
+ scheduleCanceledResultTo(resultToApp, resultTo, intent,
+ userId, brOptions, callingUid, callerPackage);
+ // Returning success seems to be the pattern here
+ return ActivityManager.BROADCAST_SUCCESS;
+ }
+ break;
+ }
+
+ if (Intent.ACTION_PACKAGE_ADDED.equals(action)
+ || Intent.ACTION_PACKAGE_REMOVED.equals(action)
+ || Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
+ final int uid = getUidFromIntent(intent);
+ if (uid != -1) {
+ final UidRecord uidRec = mService.mProcessList.getUidRecordLOSP(uid);
+ if (uidRec != null) {
+ uidRec.updateHasInternetPermission();
+ }
+ }
+ }
+ }
+
+ // Add to the sticky list if requested.
+ if (sticky) {
+ if (mService.checkPermission(android.Manifest.permission.BROADCAST_STICKY,
+ callingPid, callingUid)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg =
+ "Permission Denial: broadcastIntent() requesting a sticky broadcast from"
+ + " pid="
+ + callingPid
+ + ", uid="
+ + callingUid
+ + " requires "
+ + android.Manifest.permission.BROADCAST_STICKY;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ if (requiredPermissions != null && requiredPermissions.length > 0) {
+ Slog.w(TAG, "Can't broadcast sticky intent " + intent
+ + " and enforce permissions " + Arrays.toString(requiredPermissions));
+ scheduleCanceledResultTo(resultToApp, resultTo, intent,
+ userId, brOptions, callingUid, callerPackage);
+ return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;
+ }
+ if (intent.getComponent() != null) {
+ throw new SecurityException(
+ "Sticky broadcasts can't target a specific component");
+ }
+ synchronized (mStickyBroadcasts) {
+ // We use userId directly here, since the "all" target is maintained
+ // as a separate set of sticky broadcasts.
+ if (userId != UserHandle.USER_ALL) {
+ // But first, if this is not a broadcast to all users, then
+ // make sure it doesn't conflict with an existing broadcast to
+ // all users.
+ ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(
+ UserHandle.USER_ALL);
+ if (stickies != null) {
+ ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
+ if (list != null) {
+ int N = list.size();
+ int i;
+ for (i = 0; i < N; i++) {
+ if (intent.filterEquals(list.get(i).intent)) {
+ throw new IllegalArgumentException("Sticky broadcast " + intent
+ + " for user " + userId
+ + " conflicts with existing global broadcast");
+ }
+ }
+ }
+ }
+ }
+ ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
+ mStickyBroadcasts.get(userId);
+ if (stickies == null) {
+ stickies = new ArrayMap<>();
+ mStickyBroadcasts.put(userId, stickies);
+ }
+ ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
+ if (list == null) {
+ list = new ArrayList<>();
+ stickies.put(intent.getAction(), list);
+ }
+ final boolean deferUntilActive = BroadcastRecord.calculateDeferUntilActive(
+ callingUid, brOptions, resultTo, ordered,
+ BroadcastRecord.calculateUrgent(intent, brOptions));
+ final int stickiesCount = list.size();
+ int i;
+ for (i = 0; i < stickiesCount; i++) {
+ if (intent.filterEquals(list.get(i).intent)) {
+ // This sticky already exists, replace it.
+ list.set(i, StickyBroadcast.create(new Intent(intent), deferUntilActive,
+ callingUid, callerAppProcessState, resolvedType));
+ break;
+ }
+ }
+ if (i >= stickiesCount) {
+ list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
+ callingUid, callerAppProcessState, resolvedType));
+ }
+ }
+ }
+
+ int[] users;
+ if (userId == UserHandle.USER_ALL) {
+ // Caller wants broadcast to go to all started users.
+ users = mService.mUserController.getStartedUserArray();
+ } else {
+ // Caller wants broadcast to go to one specific user.
+ users = new int[] {userId};
+ }
+
+ var args = new SaferIntentUtils.IntentArgs(intent, resolvedType,
+ true /* isReceiver */, true /* resolveForStart */, callingUid, callingPid);
+ args.platformCompat = mService.mPlatformCompat;
+
+ // Figure out who all will receive this broadcast.
+ final int cookie = BroadcastQueue.traceBegin("queryReceivers");
+ List receivers = null;
+ List<BroadcastFilter> registeredReceivers = null;
+ // Need to resolve the intent to interested receivers...
+ if ((intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+ receivers = collectReceiverComponents(
+ intent, resolvedType, callingUid, callingPid, users, broadcastAllowList);
+ }
+ if (intent.getComponent() == null) {
+ final PackageDataSnapshot snapshot = mService.getPackageManagerInternal().snapshot();
+ if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
+ // Query one target user at a time, excluding shell-restricted users
+ for (int i = 0; i < users.length; i++) {
+ if (mService.mUserController.hasUserRestriction(
+ UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {
+ continue;
+ }
+ List<BroadcastFilter> registeredReceiversForUser =
+ mReceiverResolver.queryIntent(snapshot, intent,
+ resolvedType, false /*defaultOnly*/, users[i]);
+ if (registeredReceivers == null) {
+ registeredReceivers = registeredReceiversForUser;
+ } else if (registeredReceiversForUser != null) {
+ registeredReceivers.addAll(registeredReceiversForUser);
+ }
+ }
+ } else {
+ registeredReceivers = mReceiverResolver.queryIntent(snapshot, intent,
+ resolvedType, false /*defaultOnly*/, userId);
+ }
+ if (registeredReceivers != null) {
+ SaferIntentUtils.blockNullAction(args, registeredReceivers);
+ }
+ }
+ BroadcastQueue.traceEnd(cookie);
+
+ final boolean replacePending =
+ (intent.getFlags() & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
+
+ if (DEBUG_BROADCAST) {
+ Slog.v(TAG_BROADCAST, "Enqueueing broadcast: " + intent.getAction()
+ + " replacePending=" + replacePending);
+ }
+ if (registeredReceivers != null && broadcastAllowList != null) {
+ // if a uid whitelist was provided, remove anything in the application space that wasn't
+ // in it.
+ for (int i = registeredReceivers.size() - 1; i >= 0; i--) {
+ final int owningAppId = UserHandle.getAppId(registeredReceivers.get(i).owningUid);
+ if (owningAppId >= Process.FIRST_APPLICATION_UID
+ && Arrays.binarySearch(broadcastAllowList, owningAppId) < 0) {
+ registeredReceivers.remove(i);
+ }
+ }
+ }
+
+ int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
+
+ // Merge into one list.
+ int ir = 0;
+ if (receivers != null) {
+ // A special case for PACKAGE_ADDED: do not allow the package
+ // being added to see this broadcast. This prevents them from
+ // using this as a back door to get run as soon as they are
+ // installed. Maybe in the future we want to have a special install
+ // broadcast or such for apps, but we'd like to deliberately make
+ // this decision.
+ String[] skipPackages = null;
+ if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())
+ || Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())
+ || Intent.ACTION_PACKAGE_DATA_CLEARED.equals(intent.getAction())) {
+ Uri data = intent.getData();
+ if (data != null) {
+ String pkgName = data.getSchemeSpecificPart();
+ if (pkgName != null) {
+ skipPackages = new String[] { pkgName };
+ }
+ }
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {
+ skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ }
+ if (skipPackages != null && (skipPackages.length > 0)) {
+ for (String skipPackage : skipPackages) {
+ if (skipPackage != null) {
+ int NT = receivers.size();
+ for (int it = 0; it < NT; it++) {
+ ResolveInfo curt = (ResolveInfo) receivers.get(it);
+ if (curt.activityInfo.packageName.equals(skipPackage)) {
+ receivers.remove(it);
+ it--;
+ NT--;
+ }
+ }
+ }
+ }
+ }
+
+ int NT = receivers != null ? receivers.size() : 0;
+ int it = 0;
+ ResolveInfo curt = null;
+ BroadcastFilter curr = null;
+ while (it < NT && ir < NR) {
+ if (curt == null) {
+ curt = (ResolveInfo) receivers.get(it);
+ }
+ if (curr == null) {
+ curr = registeredReceivers.get(ir);
+ }
+ if (curr.getPriority() >= curt.priority) {
+ // Insert this broadcast record into the final list.
+ receivers.add(it, curr);
+ ir++;
+ curr = null;
+ it++;
+ NT++;
+ } else {
+ // Skip to the next ResolveInfo in the final list.
+ it++;
+ curt = null;
+ }
+ }
+ }
+ while (ir < NR) {
+ if (receivers == null) {
+ receivers = new ArrayList();
+ }
+ receivers.add(registeredReceivers.get(ir));
+ ir++;
+ }
+
+ if (isCallerSystem) {
+ checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
+ isProtectedBroadcast, receivers);
+ }
+
+ if ((receivers != null && receivers.size() > 0)
+ || resultTo != null) {
+ BroadcastQueue queue = mBroadcastQueue;
+ SaferIntentUtils.filterNonExportedComponents(args, receivers);
+ BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
+ callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
+ requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
+ receivers, resultToApp, resultTo, resultCode, resultData, resultExtras,
+ ordered, sticky, false, userId,
+ backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
+ callerAppProcessState);
+ broadcastSentEventRecord.setBroadcastRecord(r);
+
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
+ queue.enqueueBroadcastLocked(r);
+ } else {
+ // There was nobody interested in the broadcast, but we still want to record
+ // that it happened.
+ if (intent.getComponent() == null && intent.getPackage() == null
+ && (intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+ // This was an implicit broadcast... let's record it for posterity.
+ addBroadcastStatLocked(intent.getAction(), callerPackage, 0, 0, 0);
+ }
+ }
+
+ return ActivityManager.BROADCAST_SUCCESS;
+ }
+
+ @GuardedBy("mService")
+ private void scheduleCanceledResultTo(ProcessRecord resultToApp, IIntentReceiver resultTo,
+ Intent intent, int userId, BroadcastOptions options, int callingUid,
+ String callingPackage) {
+ if (resultTo == null) {
+ return;
+ }
+ final ProcessRecord app = resultToApp;
+ final IApplicationThread thread = (app != null) ? app.getOnewayThread() : null;
+ if (thread != null) {
+ try {
+ final boolean shareIdentity = (options != null && options.isShareIdentityEnabled());
+ thread.scheduleRegisteredReceiver(
+ resultTo, intent, Activity.RESULT_CANCELED, null, null,
+ false, false, true, userId, app.mState.getReportedProcState(),
+ shareIdentity ? callingUid : Process.INVALID_UID,
+ shareIdentity ? callingPackage : null);
+ } catch (RemoteException e) {
+ final String msg = "Failed to schedule result of " + intent + " via "
+ + app + ": " + e;
+ app.killLocked("Can't schedule resultTo", ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
+ Slog.d(TAG, msg);
+ }
+ }
+ }
+
+ @GuardedBy("mService")
+ private int getRealProcessStateLocked(ProcessRecord app, int pid) {
+ if (app == null) {
+ synchronized (mService.mPidsSelfLocked) {
+ app = mService.mPidsSelfLocked.get(pid);
+ }
+ }
+ if (app != null && app.getThread() != null && !app.isKilled()) {
+ return app.mState.getCurProcState();
+ }
+ return PROCESS_STATE_NONEXISTENT;
+ }
+
+ @GuardedBy("mService")
+ private int getRealUidStateLocked(ProcessRecord app, int pid) {
+ if (app == null) {
+ synchronized (mService.mPidsSelfLocked) {
+ app = mService.mPidsSelfLocked.get(pid);
+ }
+ }
+ if (app != null && app.getThread() != null && !app.isKilled()) {
+ final UidRecord uidRecord = app.getUidRecord();
+ if (uidRecord != null) {
+ return uidRecord.getCurProcState();
+ }
+ }
+ return PROCESS_STATE_NONEXISTENT;
+ }
+
+ @VisibleForTesting
+ ArrayList<StickyBroadcast> getStickyBroadcastsForTest(String action, int userId) {
+ synchronized (mStickyBroadcasts) {
+ final ArrayMap<String, ArrayList<StickyBroadcast>> stickyBroadcasts =
+ mStickyBroadcasts.get(userId);
+ if (stickyBroadcasts == null) {
+ return null;
+ }
+ return stickyBroadcasts.get(action);
+ }
+ }
+
+ void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) {
+ // Refuse possible leaked file descriptors
+ if (intent != null && intent.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Intent");
+ }
+
+ userId = mService.mUserController.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, true, ALLOW_NON_FULL,
+ "removeStickyBroadcast", null);
+
+ if (mService.checkCallingPermission(android.Manifest.permission.BROADCAST_STICKY)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: unbroadcastIntent() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + android.Manifest.permission.BROADCAST_STICKY;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ synchronized (mStickyBroadcasts) {
+ ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
+ if (stickies != null) {
+ ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
+ if (list != null) {
+ int N = list.size();
+ int i;
+ for (i = 0; i < N; i++) {
+ if (intent.filterEquals(list.get(i).intent)) {
+ list.remove(i);
+ break;
+ }
+ }
+ if (list.size() <= 0) {
+ stickies.remove(intent.getAction());
+ }
+ }
+ if (stickies.size() <= 0) {
+ mStickyBroadcasts.remove(userId);
+ }
+ }
+ }
+ }
+
+ void finishReceiver(IBinder caller, int resultCode, String resultData,
+ Bundle resultExtras, boolean resultAbort, int flags) {
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Finish receiver: " + caller);
+
+ // Refuse possible leaked file descriptors
+ if (resultExtras != null && resultExtras.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Bundle");
+ }
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mService) {
+ final ProcessRecord callerApp = mService.getRecordForAppLOSP(caller);
+ if (callerApp == null) {
+ Slog.w(TAG, "finishReceiver: no app for " + caller);
+ return;
+ }
+
+ mBroadcastQueue.finishReceiverLocked(callerApp, resultCode,
+ resultData, resultExtras, resultAbort, true);
+ // updateOomAdjLocked() will be done here
+ mService.trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
+ }
+
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ /**
+ * @return uid from the extra field {@link Intent#EXTRA_UID} if present, Otherwise -1
+ */
+ private int getUidFromIntent(Intent intent) {
+ if (intent == null) {
+ return -1;
+ }
+ final Bundle intentExtras = intent.getExtras();
+ return intent.hasExtra(Intent.EXTRA_UID)
+ ? intentExtras.getInt(Intent.EXTRA_UID) : -1;
+ }
+
+ final void rotateBroadcastStatsIfNeededLocked() {
+ final long now = SystemClock.elapsedRealtime();
+ if (mCurBroadcastStats == null
+ || (mCurBroadcastStats.mStartRealtime + (24 * 60 * 60 * 1000) < now)) {
+ mLastBroadcastStats = mCurBroadcastStats;
+ if (mLastBroadcastStats != null) {
+ mLastBroadcastStats.mEndRealtime = SystemClock.elapsedRealtime();
+ mLastBroadcastStats.mEndUptime = SystemClock.uptimeMillis();
+ }
+ mCurBroadcastStats = new BroadcastStats();
+ }
+ }
+
+ final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
+ int skipCount, long dispatchTime) {
+ rotateBroadcastStatsIfNeededLocked();
+ mCurBroadcastStats.addBroadcast(action, srcPackage, receiveCount, skipCount, dispatchTime);
+ }
+
+ final void addBackgroundCheckViolationLocked(String action, String targetPackage) {
+ rotateBroadcastStatsIfNeededLocked();
+ mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage);
+ }
+
+ final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
+ final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
+ final String callerPackage = info != null ? info.packageName : original.callerPackage;
+ if (callerPackage != null) {
+ mService.mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
+ original.callingUid, 0, callerPackage).sendToTarget();
+ }
+ }
+
+ final Intent verifyBroadcastLocked(Intent intent) {
+ if (intent != null) {
+ intent.prepareToEnterSystemServer();
+ }
+
+ int flags = intent.getFlags();
+
+ if (!mService.mProcessesReady) {
+ // if the caller really truly claims to know what they're doing, go
+ // ahead and allow the broadcast without launching any receivers
+ if ((flags & Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) != 0) {
+ // This will be turned into a FLAG_RECEIVER_REGISTERED_ONLY later on if needed.
+ } else if ((flags & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+ Slog.e(TAG, "Attempt to launch receivers of broadcast intent " + intent
+ + " before boot completion");
+ throw new IllegalStateException("Cannot broadcast before boot completed");
+ }
+ }
+
+ if ((flags & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
+ throw new IllegalArgumentException(
+ "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
+ }
+
+ if ((flags & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
+ switch (Binder.getCallingUid()) {
+ case ROOT_UID:
+ case SHELL_UID:
+ break;
+ default:
+ Slog.w(TAG, "Removing FLAG_RECEIVER_FROM_SHELL because caller is UID "
+ + Binder.getCallingUid());
+ intent.removeFlags(Intent.FLAG_RECEIVER_FROM_SHELL);
+ break;
+ }
+ }
+
+ return intent;
+ }
+
+ private ArraySet<String> getBackgroundLaunchBroadcasts() {
+ if (mBackgroundLaunchBroadcasts == null) {
+ mBackgroundLaunchBroadcasts = SystemConfig.getInstance().getAllowImplicitBroadcasts();
+ }
+ return mBackgroundLaunchBroadcasts;
+ }
+
+ private boolean isInstantApp(ProcessRecord record, @Nullable String callerPackage, int uid) {
+ if (UserHandle.getAppId(uid) < FIRST_APPLICATION_UID) {
+ return false;
+ }
+ // Easy case -- we have the app's ProcessRecord.
+ if (record != null) {
+ return record.info.isInstantApp();
+ }
+ // Otherwise check with PackageManager.
+ IPackageManager pm = AppGlobals.getPackageManager();
+ try {
+ if (callerPackage == null) {
+ final String[] packageNames = pm.getPackagesForUid(uid);
+ if (packageNames == null || packageNames.length == 0) {
+ throw new IllegalArgumentException("Unable to determine caller package name");
+ }
+ // Instant Apps can't use shared uids, so its safe to only check the first package.
+ callerPackage = packageNames[0];
+ }
+ mService.mAppOpsService.checkPackage(uid, callerPackage);
+ return pm.isInstantApp(callerPackage, UserHandle.getUserId(uid));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error looking up if " + callerPackage + " is an instant app.", e);
+ return true;
+ }
+ }
+
+ private String getWearRemoteIntentAction() {
+ return mContext.getResources().getString(
+ com.android.internal.R.string.config_wearRemoteIntentAction);
+ }
+
+ private void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
+ mService.mProcessList.sendPackageBroadcastLocked(cmd, packages, userId);
+ }private List<ResolveInfo> collectReceiverComponents(
+ Intent intent, String resolvedType, int callingUid, int callingPid,
+ int[] users, int[] broadcastAllowList) {
+ // TODO: come back and remove this assumption to triage all broadcasts
+ long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;
+
+ List<ResolveInfo> receivers = null;
+ HashSet<ComponentName> singleUserReceivers = null;
+ boolean scannedFirstReceivers = false;
+ for (int user : users) {
+ // Skip users that have Shell restrictions
+ if (callingUid == SHELL_UID
+ && mService.mUserController.hasUserRestriction(
+ UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
+ continue;
+ }
+ List<ResolveInfo> newReceivers = mService.mPackageManagerInt.queryIntentReceivers(
+ intent, resolvedType, pmFlags, callingUid, callingPid, user, /* forSend */true);
+ if (user != UserHandle.USER_SYSTEM && newReceivers != null) {
+ // If this is not the system user, we need to check for
+ // any receivers that should be filtered out.
+ for (int i = 0; i < newReceivers.size(); i++) {
+ ResolveInfo ri = newReceivers.get(i);
+ if ((ri.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
+ newReceivers.remove(i);
+ i--;
+ }
+ }
+ }
+ // Replace the alias receivers with their targets.
+ if (newReceivers != null) {
+ for (int i = newReceivers.size() - 1; i >= 0; i--) {
+ final ResolveInfo ri = newReceivers.get(i);
+ final ComponentAliasResolver.Resolution<ResolveInfo> resolution =
+ mService.mComponentAliasResolver.resolveReceiver(intent, ri,
+ resolvedType, pmFlags, user, callingUid, callingPid);
+ if (resolution == null) {
+ // It was an alias, but the target was not found.
+ newReceivers.remove(i);
+ continue;
+ }
+ if (resolution.isAlias()) {
+ newReceivers.set(i, resolution.getTarget());
+ }
+ }
+ }
+ if (newReceivers != null && newReceivers.size() == 0) {
+ newReceivers = null;
+ }
+
+ if (receivers == null) {
+ receivers = newReceivers;
+ } else if (newReceivers != null) {
+ // We need to concatenate the additional receivers
+ // found with what we have do far. This would be easy,
+ // but we also need to de-dup any receivers that are
+ // singleUser.
+ if (!scannedFirstReceivers) {
+ // Collect any single user receivers we had already retrieved.
+ scannedFirstReceivers = true;
+ for (int i = 0; i < receivers.size(); i++) {
+ ResolveInfo ri = receivers.get(i);
+ if ((ri.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
+ ComponentName cn = new ComponentName(
+ ri.activityInfo.packageName, ri.activityInfo.name);
+ if (singleUserReceivers == null) {
+ singleUserReceivers = new HashSet<ComponentName>();
+ }
+ singleUserReceivers.add(cn);
+ }
+ }
+ }
+ // Add the new results to the existing results, tracking
+ // and de-dupping single user receivers.
+ for (int i = 0; i < newReceivers.size(); i++) {
+ ResolveInfo ri = newReceivers.get(i);
+ if ((ri.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
+ ComponentName cn = new ComponentName(
+ ri.activityInfo.packageName, ri.activityInfo.name);
+ if (singleUserReceivers == null) {
+ singleUserReceivers = new HashSet<ComponentName>();
+ }
+ if (!singleUserReceivers.contains(cn)) {
+ singleUserReceivers.add(cn);
+ receivers.add(ri);
+ }
+ } else {
+ receivers.add(ri);
+ }
+ }
+ }
+ }
+ if (receivers != null && broadcastAllowList != null) {
+ for (int i = receivers.size() - 1; i >= 0; i--) {
+ final int receiverAppId = UserHandle.getAppId(
+ receivers.get(i).activityInfo.applicationInfo.uid);
+ if (receiverAppId >= Process.FIRST_APPLICATION_UID
+ && Arrays.binarySearch(broadcastAllowList, receiverAppId) < 0) {
+ receivers.remove(i);
+ }
+ }
+ }
+ return receivers;
+ }
+
+ private void checkBroadcastFromSystem(Intent intent, ProcessRecord callerApp,
+ String callerPackage, int callingUid, boolean isProtectedBroadcast, List receivers) {
+ if ((intent.getFlags() & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
+ // Don't yell about broadcasts sent via shell
+ return;
+ }
+
+ final String action = intent.getAction();
+ if (isProtectedBroadcast
+ || Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
+ || Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(action)
+ || Intent.ACTION_MEDIA_BUTTON.equals(action)
+ || Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action)
+ || Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(action)
+ || Intent.ACTION_MASTER_CLEAR.equals(action)
+ || Intent.ACTION_FACTORY_RESET.equals(action)
+ || AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
+ || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)
+ || TelephonyManager.ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE.equals(action)
+ || SuggestionSpan.ACTION_SUGGESTION_PICKED.equals(action)
+ || AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION.equals(action)
+ || AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION.equals(action)) {
+ // Broadcast is either protected, or it's a public action that
+ // we've relaxed, so it's fine for system internals to send.
+ return;
+ }
+
+ // This broadcast may be a problem... but there are often system components that
+ // want to send an internal broadcast to themselves, which is annoying to have to
+ // explicitly list each action as a protected broadcast, so we will check for that
+ // one safe case and allow it: an explicit broadcast, only being received by something
+ // that has protected itself.
+ if (intent.getPackage() != null || intent.getComponent() != null) {
+ if (receivers == null || receivers.size() == 0) {
+ // Intent is explicit and there's no receivers.
+ // This happens, e.g. , when a system component sends a broadcast to
+ // its own runtime receiver, and there's no manifest receivers for it,
+ // because this method is called twice for each broadcast,
+ // for runtime receivers and manifest receivers and the later check would find
+ // no receivers.
+ return;
+ }
+ boolean allProtected = true;
+ for (int i = receivers.size() - 1; i >= 0; i--) {
+ Object target = receivers.get(i);
+ if (target instanceof ResolveInfo) {
+ ResolveInfo ri = (ResolveInfo) target;
+ if (ri.activityInfo.exported && ri.activityInfo.permission == null) {
+ allProtected = false;
+ break;
+ }
+ } else {
+ BroadcastFilter bf = (BroadcastFilter) target;
+ if (bf.exported && bf.requiredPermission == null) {
+ allProtected = false;
+ break;
+ }
+ }
+ }
+ if (allProtected) {
+ // All safe!
+ return;
+ }
+ }
+
+ // The vast majority of broadcasts sent from system internals
+ // should be protected to avoid security holes, so yell loudly
+ // to ensure we examine these cases.
+ if (callerApp != null) {
+ Log.wtf(TAG, "Sending non-protected broadcast " + action
+ + " from system " + callerApp.toShortString() + " pkg " + callerPackage,
+ new Throwable());
+ } else {
+ Log.wtf(TAG, "Sending non-protected broadcast " + action
+ + " from system uid " + UserHandle.formatUid(callingUid)
+ + " pkg " + callerPackage,
+ new Throwable());
+ }
+ }
+
+ // Apply permission policy around the use of specific broadcast options
+ void enforceBroadcastOptionPermissionsInternal(
+ @Nullable Bundle options, int callingUid) {
+ enforceBroadcastOptionPermissionsInternal(BroadcastOptions.fromBundleNullable(options),
+ callingUid);
+ }
+
+ private void enforceBroadcastOptionPermissionsInternal(
+ @Nullable BroadcastOptions options, int callingUid) {
+ if (options != null && callingUid != Process.SYSTEM_UID) {
+ if (options.isAlarmBroadcast()) {
+ if (DEBUG_BROADCAST_LIGHT) {
+ Slog.w(TAG, "Non-system caller " + callingUid
+ + " may not flag broadcast as alarm");
+ }
+ throw new SecurityException(
+ "Non-system callers may not flag broadcasts as alarm");
+ }
+ if (options.isInteractive()) {
+ mService.enforceCallingPermission(
+ android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE,
+ "setInteractive");
+ }
+ }
+ }
+
+ void startBroadcastObservers() {
+ mBroadcastQueue.start(mContext.getContentResolver());
+ }
+
+ void removeStickyBroadcasts(int userId) {
+ synchronized (mStickyBroadcasts) {
+ mStickyBroadcasts.remove(userId);
+ }
+ }
+
+ @NeverCompile
+ void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+ int opti, boolean dumpAll, String dumpPackage) {
+ boolean dumpConstants = true;
+ boolean dumpHistory = true;
+ boolean needSep = false;
+ boolean onlyHistory = false;
+ boolean printedAnything = false;
+ boolean onlyReceivers = false;
+ int filteredUid = Process.INVALID_UID;
+
+ if ("history".equals(dumpPackage)) {
+ if (opti < args.length && "-s".equals(args[opti])) {
+ dumpAll = false;
+ }
+ onlyHistory = true;
+ dumpPackage = null;
+ }
+ if ("receivers".equals(dumpPackage)) {
+ onlyReceivers = true;
+ dumpPackage = null;
+ if (opti + 2 <= args.length) {
+ for (int i = opti; i < args.length; i++) {
+ String arg = args[i];
+ switch (arg) {
+ case "--uid":
+ filteredUid = getIntArg(pw, args, ++i, Process.INVALID_UID);
+ if (filteredUid == Process.INVALID_UID) {
+ return;
+ }
+ break;
+ default:
+ pw.printf("Invalid argument at index %d: %s\n", i, arg);
+ return;
+ }
+ }
+ }
+ }
+ if (DEBUG_BROADCAST) {
+ Slogf.d(TAG_BROADCAST, "dumpBroadcastsLocked(): dumpPackage=%s, onlyHistory=%b, "
+ + "onlyReceivers=%b, filteredUid=%d", dumpPackage, onlyHistory,
+ onlyReceivers, filteredUid);
+ }
+
+ pw.println("ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts)");
+ if (!onlyHistory && dumpAll) {
+ if (mRegisteredReceivers.size() > 0) {
+ boolean printed = false;
+ Iterator it = mRegisteredReceivers.values().iterator();
+ while (it.hasNext()) {
+ ReceiverList r = (ReceiverList) it.next();
+ if (dumpPackage != null && (r.app == null
+ || !dumpPackage.equals(r.app.info.packageName))) {
+ continue;
+ }
+ if (filteredUid != Process.INVALID_UID && filteredUid != r.app.uid) {
+ if (DEBUG_BROADCAST) {
+ Slogf.v(TAG_BROADCAST, "dumpBroadcastsLocked(): skipping receiver whose"
+ + " uid (%d) is not %d: %s", r.app.uid, filteredUid, r.app);
+ }
+ continue;
+ }
+ if (!printed) {
+ pw.println(" Registered Receivers:");
+ needSep = true;
+ printed = true;
+ printedAnything = true;
+ }
+ pw.print(" * "); pw.println(r);
+ r.dump(pw, " ");
+ }
+ } else {
+ if (onlyReceivers) {
+ pw.println(" (no registered receivers)");
+ }
+ }
+
+ if (!onlyReceivers) {
+ if (mReceiverResolver.dump(pw, needSep
+ ? "\n Receiver Resolver Table:" : " Receiver Resolver Table:",
+ " ", dumpPackage, false, false)) {
+ needSep = true;
+ printedAnything = true;
+ }
+ }
+ }
+
+ if (!onlyReceivers) {
+ needSep = mBroadcastQueue.dumpLocked(fd, pw, args, opti,
+ dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
+ printedAnything |= needSep;
+ }
+
+ needSep = true;
+
+ synchronized (mStickyBroadcasts) {
+ if (!onlyHistory && !onlyReceivers && mStickyBroadcasts != null
+ && dumpPackage == null) {
+ for (int user = 0; user < mStickyBroadcasts.size(); user++) {
+ if (needSep) {
+ pw.println();
+ }
+ needSep = true;
+ printedAnything = true;
+ pw.print(" Sticky broadcasts for user ");
+ pw.print(mStickyBroadcasts.keyAt(user));
+ pw.println(":");
+ StringBuilder sb = new StringBuilder(128);
+ for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
+ : mStickyBroadcasts.valueAt(user).entrySet()) {
+ pw.print(" * Sticky action ");
+ pw.print(ent.getKey());
+ if (dumpAll) {
+ pw.println(":");
+ ArrayList<StickyBroadcast> broadcasts = ent.getValue();
+ final int N = broadcasts.size();
+ for (int i = 0; i < N; i++) {
+ final Intent intent = broadcasts.get(i).intent;
+ final boolean deferUntilActive = broadcasts.get(i).deferUntilActive;
+ sb.setLength(0);
+ sb.append(" Intent: ");
+ intent.toShortString(sb, false, true, false, false);
+ pw.print(sb);
+ if (deferUntilActive) {
+ pw.print(" [D]");
+ }
+ pw.println();
+ pw.print(" originalCallingUid: ");
+ pw.println(broadcasts.get(i).originalCallingUid);
+ pw.println();
+ Bundle bundle = intent.getExtras();
+ if (bundle != null) {
+ pw.print(" extras: ");
+ pw.println(bundle);
+ }
+ }
+ } else {
+ pw.println("");
+ }
+ }
+ }
+ }
+ }
+
+ if (!onlyHistory && !onlyReceivers && dumpAll) {
+ pw.println();
+ pw.println(" Queue " + mBroadcastQueue.toString() + ": "
+ + mBroadcastQueue.describeStateLocked());
+ pw.println(" mHandler:");
+ mService.mHandler.dump(new PrintWriterPrinter(pw), " ");
+ needSep = true;
+ printedAnything = true;
+ }
+
+ if (!printedAnything) {
+ pw.println(" (nothing)");
+ }
+ }
+
+ /**
+ * Gets an {@code int} argument from the given {@code index} on {@code args}, logging an error
+ * message on {@code pw} when it cannot be parsed.
+ *
+ * Returns {@code int} argument or {@code invalidValue} if it could not be parsed.
+ */
+ private static int getIntArg(PrintWriter pw, String[] args, int index, int invalidValue) {
+ if (index > args.length) {
+ pw.println("Missing argument");
+ return invalidValue;
+ }
+ String arg = args[index];
+ try {
+ return Integer.parseInt(arg);
+ } catch (Exception e) {
+ pw.printf("Non-numeric argument at index %d: %s\n", index, arg);
+ return invalidValue;
+ }
+ }
+
+ @NeverCompile
+ void dumpBroadcastStatsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+ int opti, boolean dumpAll, String dumpPackage) {
+ if (mCurBroadcastStats == null) {
+ return;
+ }
+
+ pw.println("ACTIVITY MANAGER BROADCAST STATS STATE (dumpsys activity broadcast-stats)");
+ final long now = SystemClock.elapsedRealtime();
+ if (mLastBroadcastStats != null) {
+ pw.print(" Last stats (from ");
+ TimeUtils.formatDuration(mLastBroadcastStats.mStartRealtime, now, pw);
+ pw.print(" to ");
+ TimeUtils.formatDuration(mLastBroadcastStats.mEndRealtime, now, pw);
+ pw.print(", ");
+ TimeUtils.formatDuration(mLastBroadcastStats.mEndUptime
+ - mLastBroadcastStats.mStartUptime, pw);
+ pw.println(" uptime):");
+ if (!mLastBroadcastStats.dumpStats(pw, " ", dumpPackage)) {
+ pw.println(" (nothing)");
+ }
+ pw.println();
+ }
+ pw.print(" Current stats (from ");
+ TimeUtils.formatDuration(mCurBroadcastStats.mStartRealtime, now, pw);
+ pw.print(" to now, ");
+ TimeUtils.formatDuration(SystemClock.uptimeMillis()
+ - mCurBroadcastStats.mStartUptime, pw);
+ pw.println(" uptime):");
+ if (!mCurBroadcastStats.dumpStats(pw, " ", dumpPackage)) {
+ pw.println(" (nothing)");
+ }
+ }
+
+ @NeverCompile
+ void dumpBroadcastStatsCheckinLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+ int opti, boolean fullCheckin, String dumpPackage) {
+ if (mCurBroadcastStats == null) {
+ return;
+ }
+
+ if (mLastBroadcastStats != null) {
+ mLastBroadcastStats.dumpCheckinStats(pw, dumpPackage);
+ if (fullCheckin) {
+ mLastBroadcastStats = null;
+ return;
+ }
+ }
+ mCurBroadcastStats.dumpCheckinStats(pw, dumpPackage);
+ if (fullCheckin) {
+ mCurBroadcastStats = null;
+ }
+ }
+
+ void writeBroadcastsToProtoLocked(ProtoOutputStream proto) {
+ if (mRegisteredReceivers.size() > 0) {
+ Iterator it = mRegisteredReceivers.values().iterator();
+ while (it.hasNext()) {
+ ReceiverList r = (ReceiverList) it.next();
+ r.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_LIST);
+ }
+ }
+ mReceiverResolver.dumpDebug(proto,
+ ActivityManagerServiceDumpBroadcastsProto.RECEIVER_RESOLVER);
+ mBroadcastQueue.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
+ synchronized (mStickyBroadcasts) {
+ for (int user = 0; user < mStickyBroadcasts.size(); user++) {
+ long token = proto.start(
+ ActivityManagerServiceDumpBroadcastsProto.STICKY_BROADCASTS);
+ proto.write(StickyBroadcastProto.USER, mStickyBroadcasts.keyAt(user));
+ for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
+ : mStickyBroadcasts.valueAt(user).entrySet()) {
+ long actionToken = proto.start(StickyBroadcastProto.ACTIONS);
+ proto.write(StickyBroadcastProto.StickyAction.NAME, ent.getKey());
+ for (StickyBroadcast broadcast : ent.getValue()) {
+ broadcast.intent.dumpDebug(proto, StickyBroadcastProto.StickyAction.INTENTS,
+ false, true, true, false);
+ }
+ proto.end(actionToken);
+ }
+ proto.end(token);
+ }
+ }
+
+ long handlerToken = proto.start(ActivityManagerServiceDumpBroadcastsProto.HANDLER);
+ proto.write(ActivityManagerServiceDumpBroadcastsProto.MainHandler.HANDLER,
+ mService.mHandler.toString());
+ mService.mHandler.getLooper().dumpDebug(proto,
+ ActivityManagerServiceDumpBroadcastsProto.MainHandler.LOOPER);
+ proto.end(handlerToken);
+ }
+
+ @VisibleForTesting
+ static final class StickyBroadcast {
+ public Intent intent;
+ public boolean deferUntilActive;
+ public int originalCallingUid;
+ /** The snapshot process state of the app who sent this broadcast */
+ public int originalCallingAppProcessState;
+ public String resolvedDataType;
+
+ public static StickyBroadcast create(Intent intent, boolean deferUntilActive,
+ int originalCallingUid, int originalCallingAppProcessState,
+ String resolvedDataType) {
+ final StickyBroadcast b = new StickyBroadcast();
+ b.intent = intent;
+ b.deferUntilActive = deferUntilActive;
+ b.originalCallingUid = originalCallingUid;
+ b.originalCallingAppProcessState = originalCallingAppProcessState;
+ b.resolvedDataType = resolvedDataType;
+ return b;
+ }
+
+ @Override
+ public String toString() {
+ return "{intent=" + intent + ", defer=" + deferUntilActive + ", originalCallingUid="
+ + originalCallingUid + ", originalCallingAppProcessState="
+ + originalCallingAppProcessState + ", type=" + resolvedDataType + "}";
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java
index 7d26004..5488446 100644
--- a/services/core/java/com/android/server/display/BrightnessSetting.java
+++ b/services/core/java/com/android/server/display/BrightnessSetting.java
@@ -150,6 +150,13 @@
}
/**
+ * Flush the brightness update that has been made to the persistent data store.
+ */
+ public void saveIfNeeded() {
+ mPersistentDataStore.saveIfNeeded();
+ }
+
+ /**
* @return The brightness for the default display in nits. Used when the underlying display
* device has changed but we want to persist the nit value.
*/
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 55a6ce7..187caba 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1181,7 +1181,10 @@
private DisplayInfo getDisplayInfoForFrameRateOverride(DisplayEventReceiver.FrameRateOverride[]
frameRateOverrides, DisplayInfo info, int callingUid) {
+ // Start with the display frame rate
float frameRateHz = info.renderFrameRate;
+
+ // If the app has a specific override, use that instead
for (DisplayEventReceiver.FrameRateOverride frameRateOverride : frameRateOverrides) {
if (frameRateOverride.uid == callingUid) {
frameRateHz = frameRateOverride.frameRateHz;
@@ -1200,18 +1203,21 @@
DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE, callingUid);
// Override the refresh rate only if it is a divisor of the current
- // refresh rate. This calculation needs to be in sync with the native code
+ // vsync rate. This calculation needs to be in sync with the native code
// in RefreshRateSelector::getFrameRateDivisor
Display.Mode currentMode = info.getMode();
- float numPeriods = currentMode.getRefreshRate() / frameRateHz;
+ float vsyncRate = currentMode.getVsyncRate();
+ float numPeriods = vsyncRate / frameRateHz;
float numPeriodsRound = Math.round(numPeriods);
if (Math.abs(numPeriods - numPeriodsRound) > THRESHOLD_FOR_REFRESH_RATES_DIVISORS) {
return info;
}
- frameRateHz = currentMode.getRefreshRate() / numPeriodsRound;
+ frameRateHz = vsyncRate / numPeriodsRound;
DisplayInfo overriddenInfo = new DisplayInfo();
overriddenInfo.copyFrom(info);
+
+ // If there is a mode that matches the override, use that one
for (Display.Mode mode : info.supportedModes) {
if (!mode.equalsExceptRefreshRate(currentMode)) {
continue;
@@ -1231,8 +1237,9 @@
return overriddenInfo;
}
}
-
overriddenInfo.refreshRateOverride = frameRateHz;
+
+ // Create a fake mode for app compat
if (!displayModeReturnsPhysicalRefreshRate) {
overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
info.supportedModes.length + 1);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index ab79713..a887f6d 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2484,6 +2484,11 @@
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
== Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+ if (screenBrightnessModeSetting == Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) {
+ // In manual mode, all brightness changes should be saved immediately.
+ mDisplayBrightnessController.saveBrightnessIfNeeded();
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index e157b05..72a91d5 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -339,6 +339,13 @@
}
/**
+ * Flush the brightness update that has been made to the persistent data store.
+ */
+ public void saveBrightnessIfNeeded() {
+ mBrightnessSetting.saveIfNeeded();
+ }
+
+ /**
* Sets the current screen brightness, and notifies the BrightnessSetting about the change.
*/
public void updateScreenBrightnessSetting(float brightnessValue, float maxBrightness) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index f34b4e9..7ce9ee6 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3037,6 +3037,7 @@
intent.putExtra("input_method_id", id);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
+ bindingController.unbindCurrentMethod();
unbindCurrentClientLocked(UnbindReason.SWITCH_IME, userId);
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index ee3f48d..06d1285 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -485,7 +485,7 @@
newConfig = mConfig.copy();
ZenRule rule = new ZenRule();
populateZenRule(pkg, automaticZenRule, rule, origin, /* isNew= */ true);
- rule = maybeRestoreRemovedRule(newConfig, rule, automaticZenRule, origin);
+ rule = maybeRestoreRemovedRule(newConfig, pkg, rule, automaticZenRule, origin);
newConfig.automaticRules.put(rule.id, rule);
maybeReplaceDefaultRule(newConfig, automaticZenRule);
@@ -498,7 +498,7 @@
}
@GuardedBy("mConfigLock")
- private ZenRule maybeRestoreRemovedRule(ZenModeConfig config, ZenRule ruleToAdd,
+ private ZenRule maybeRestoreRemovedRule(ZenModeConfig config, String pkg, ZenRule ruleToAdd,
AutomaticZenRule azrToAdd, @ConfigOrigin int origin) {
if (!Flags.modesApi()) {
return ruleToAdd;
@@ -522,10 +522,18 @@
if (origin != ORIGIN_APP) {
return ruleToAdd; // Okay to create anew.
}
+ if (Flags.modesUi()) {
+ if (!Objects.equals(ruleToRestore.pkg, pkg)
+ || !Objects.equals(ruleToRestore.component, azrToAdd.getOwner())) {
+ // Apps are not allowed to change the owner via updateAutomaticZenRule(). Thus, if
+ // they have to, delete+add is their only option.
+ return ruleToAdd;
+ }
+ }
// "Preserve" the previous rule by considering the azrToAdd an update instead.
// Only app-modifiable fields will actually be modified.
- populateZenRule(ruleToRestore.pkg, azrToAdd, ruleToRestore, origin, /* isNew= */ false);
+ populateZenRule(pkg, azrToAdd, ruleToRestore, origin, /* isNew= */ false);
return ruleToRestore;
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index ee0159d..4665a72 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -3065,10 +3065,9 @@
case DumpState.DUMP_PREFERRED_XML:
{
pw.flush();
- FileOutputStream fout = new FileOutputStream(fd);
- BufferedOutputStream str = new BufferedOutputStream(fout);
TypedXmlSerializer serializer = Xml.newFastSerializer();
- try {
+ try (BufferedOutputStream str =
+ new BufferedOutputStream(new FileOutputStream(fd))) {
serializer.setOutput(str, StandardCharsets.UTF_8.name());
serializer.startDocument(null, true);
serializer.setFeature(
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 5105fd3..ada6659b 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2231,8 +2231,9 @@
// by apexd to be more accurate.
installRequest.setScannedPackageSettingFirstInstallTimeFromReplaced(
deletedPkgSetting, allUsers);
- installRequest.setScannedPackageSettingLastUpdateTime(
- System.currentTimeMillis());
+ long currentTime = System.currentTimeMillis();
+ installRequest.setScannedPackageSettingLastUpdateTime(currentTime);
+ installRequest.setScannedPackageSettingFirstInstallTime(currentTime);
installRequest.getRemovedInfo().mBroadcastAllowList =
mPm.mAppsFilter.getVisibilityAllowList(mPm.snapshotComputer(),
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index dd2583a0d..ae7749b 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -58,6 +58,7 @@
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageUserStateInternal;
import java.io.File;
import java.util.ArrayList;
@@ -867,6 +868,14 @@
mScanResult.mPkgSetting.setLastUpdateTime(lastUpdateTim);
}
+ public void setScannedPackageSettingFirstInstallTime(long firstInstallTime) {
+ assertScanResultExists();
+ PackageUserStateInternal userState = mScanResult.mPkgSetting.getUserStates().get(mUserId);
+ if (userState != null && userState.getFirstInstallTimeMillis() == 0) {
+ mScanResult.mPkgSetting.setFirstInstallTime(firstInstallTime, mUserId);
+ }
+ }
+
public void setRemovedAppId(int appId) {
if (mRemovedInfo != null) {
mRemovedInfo.mUid = appId;
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 61fddba..0802e9e 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -443,17 +443,16 @@
// Take care of first install / last update times.
final long scanFileTime = getLastModifiedTime(parsedPackage);
- final long existingFirstInstallTime = userId == UserHandle.USER_ALL
- ? PackageStateUtils.getEarliestFirstInstallTime(pkgSetting.getUserStates())
- : pkgSetting.readUserState(userId).getFirstInstallTimeMillis();
+ final long earliestFirstInstallTime =
+ PackageStateUtils.getEarliestFirstInstallTime((pkgSetting.getUserStates()));
if (currentTime != 0) {
- if (existingFirstInstallTime == 0) {
+ if (earliestFirstInstallTime == 0) {
pkgSetting.setFirstInstallTime(currentTime, userId)
.setLastUpdateTime(currentTime);
} else if ((scanFlags & SCAN_UPDATE_TIME) != 0) {
pkgSetting.setLastUpdateTime(currentTime);
}
- } else if (existingFirstInstallTime == 0) {
+ } else if (earliestFirstInstallTime == 0) {
// We need *something*. Take time stamp of the file.
pkgSetting.setFirstInstallTime(scanFileTime, userId)
.setLastUpdateTime(scanFileTime);
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 00582bf..045d4db 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -282,6 +282,12 @@
for (int j = 0; j < idSize; j++) {
ShortcutService.writeTagValue(out, TAG_PIN, ids.valueAt(j));
}
+ if (ShortcutService.DEBUG_REBOOT) {
+ Slog.d(TAG, "Persist shortcut ids pinned by "
+ + getPackageName() + " from "
+ + up.userId + "@" + up.packageName + " ids=["
+ + String.join(", ", ids) + "]");
+ }
out.endTag(null, TAG_PACKAGE);
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 84674b2..60056eb 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1850,9 +1850,17 @@
}
getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
+ if (ShortcutService.DEBUG_REBOOT) {
+ Slog.d(TAG, "Persisting shortcuts from "
+ + getOwnerUserId() + "@" + getPackageName());
+ }
for (int j = 0; j < size; j++) {
+ final ShortcutInfo si = mShortcuts.valueAt(j);
saveShortcut(
- out, mShortcuts.valueAt(j), forBackup, getPackageInfo().isBackupAllowed());
+ out, si, forBackup, getPackageInfo().isBackupAllowed());
+ if (ShortcutService.DEBUG_REBOOT) {
+ Slog.d(TAG, si.toSimpleString());
+ }
}
if (!forBackup) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 021f7aa..25468fa 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -172,7 +172,7 @@
static final boolean DEBUG = false; // STOPSHIP if true
static final boolean DEBUG_LOAD = false; // STOPSHIP if true
static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
- static final boolean DEBUG_REBOOT = true;
+ static final boolean DEBUG_REBOOT = Build.IS_DEBUGGABLE;
@VisibleForTesting
static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 2b639fa..a683a8c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -25,6 +25,7 @@
import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_WATCH;
+import static android.os.UserHandle.USER_SYSTEM;
import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
@@ -1372,6 +1373,10 @@
}
if (isHeadlessSystemUserMode()) {
+ if (mContext.getResources()
+ .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser)) {
+ return UserHandle.USER_SYSTEM;
+ }
// Return the previous foreground user, if there is one.
final int previousUser = getPreviousFullUserToEnterForeground();
if (previousUser != UserHandle.USER_NULL) {
@@ -2514,6 +2519,38 @@
}
/**
+ * This method validates whether calling user is valid in visible background users feature.
+ * Valid user is the current user or the system or in the same profile group as the current
+ * user. Visible background users are not valid calling users.
+ */
+ public static void enforceCurrentUserIfVisibleBackgroundEnabled(@UserIdInt int currentUserId) {
+ if (!UserManager.isVisibleBackgroundUsersEnabled()) {
+ return;
+ }
+ final int callingUserId = UserHandle.getCallingUserId();
+ if (DBG) {
+ Slog.d(LOG_TAG, "enforceValidCallingUser: callingUserId=" + callingUserId
+ + " isSystemUser=" + (callingUserId == USER_SYSTEM)
+ + " currentUserId=" + currentUserId
+ + " callingPid=" + Binder.getCallingPid()
+ + " callingUid=" + Binder.getCallingUid());
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (callingUserId != USER_SYSTEM && callingUserId != currentUserId
+ && !UserManagerService.getInstance()
+ .isSameProfileGroup(callingUserId, currentUserId)) {
+ throw new SecurityException(
+ "Invalid calling user on devices that enable visible background users. "
+ + "callingUserId=" + callingUserId + " currentUserId="
+ + currentUserId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
* Gets the current and target user ids as a {@link Pair}, calling
* {@link ActivityManagerInternal} directly (and without performing any permission check).
*
diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
index 2f16419..46e779f 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -149,8 +149,7 @@
// WiFi keeps an accumulated total of stats. Keep the last WiFi stats so we can compute a delta.
// (This is unlike Bluetooth, where BatteryStatsImpl is left responsible for taking the delta.)
@GuardedBy("mWorkerLock")
- private WifiActivityEnergyInfo mLastWifiInfo =
- new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0);
+ private WifiActivityEnergyInfo mLastWifiInfo = null;
/**
* Maps an {@link EnergyConsumerType} to it's corresponding {@link EnergyConsumer#id}s,
@@ -827,8 +826,18 @@
return null;
}
+ /**
+ * Return a delta WifiActivityEnergyInfo from the last WifiActivityEnergyInfo passed to the
+ * method.
+ */
+ @VisibleForTesting
@GuardedBy("mWorkerLock")
- private WifiActivityEnergyInfo extractDeltaLocked(WifiActivityEnergyInfo latest) {
+ public WifiActivityEnergyInfo extractDeltaLocked(WifiActivityEnergyInfo latest) {
+ if (mLastWifiInfo == null) {
+ // This is the first time WifiActivityEnergyInfo has been collected since system boot.
+ // Use this first WifiActivityEnergyInfo as the starting point for all accumulations.
+ mLastWifiInfo = latest;
+ }
final long timePeriodMs = latest.getTimeSinceBootMillis()
- mLastWifiInfo.getTimeSinceBootMillis();
final long lastScanMs = mLastWifiInfo.getControllerScanDurationMillis();
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 1d3de57..d04b733 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -15487,7 +15487,8 @@
final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(which);
final long totalControllerActivityTimeMs =
computeBatteryRealtime(mClock.elapsedRealtime() * 1000, which) / 1000;
- final long sleepTimeMs = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + txTimeMs);
+ final long sleepTimeMs = Math.max(0,
+ totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + txTimeMs));
final long energyConsumedMaMs = counter.getPowerCounter().getCountLocked(which);
final long monitoredRailChargeConsumedMaMs =
counter.getMonitoredRailChargeConsumedMaMs().getCountLocked(which);
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index cda86fa..6b3b5bd 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -19,6 +19,7 @@
import static android.media.AudioManager.DEVICE_NONE;
import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
+import static android.media.tv.flags.Flags.tifUnbindInactiveTis;
import static android.media.tv.flags.Flags.kidsModeTvdbSharing;
import android.annotation.NonNull;
@@ -4515,12 +4516,14 @@
break;
}
case MSG_UPDATE_HARDWARE_TIS_BINDING:
- SomeArgs args = (SomeArgs) msg.obj;
- int userId = (int) args.arg1;
- synchronized (mLock) {
- updateHardwareTvInputServiceBindingLocked(userId);
+ if (tifUnbindInactiveTis()) {
+ SomeArgs args = (SomeArgs) msg.obj;
+ int userId = (int) args.arg1;
+ synchronized (mLock) {
+ updateHardwareTvInputServiceBindingLocked(userId);
+ }
+ args.recycle();
}
- args.recycle();
break;
default: {
Slog.w(TAG, "unhandled message code: " + msg.what);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9d91d3d..5ba8433 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -109,8 +109,6 @@
import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.INVALID_DISPLAY;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15;
import static android.view.WindowManager.ENABLE_ACTIVITY_EMBEDDING_FOR_ANDROID_15;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
@@ -341,7 +339,6 @@
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationTarget;
-import android.view.Surface.Rotation;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets;
@@ -642,9 +639,9 @@
/**
* The precomputed display insets for resolving configuration. It will be non-null if
- * {@link #shouldCreateCompatDisplayInsets} returns {@code true}.
+ * {@link #shouldCreateAppCompatDisplayInsets} returns {@code true}.
*/
- private CompatDisplayInsets mCompatDisplayInsets;
+ private AppCompatDisplayInsets mAppCompatDisplayInsets;
@VisibleForTesting
final TaskFragment.ConfigOverrideHint mResolveConfigHint;
@@ -6442,7 +6439,7 @@
mTaskSupervisor.mStoppingActivities.remove(this);
if (getDisplayArea().allResumedActivitiesComplete()) {
// Construct the compat environment at a relatively stable state if needed.
- updateCompatDisplayInsets();
+ updateAppCompatDisplayInsets();
mRootWindowContainer.executeAppTransitionForAllDisplay();
}
@@ -8074,7 +8071,7 @@
if (getRequestedConfigurationOrientation(false, requestedOrientation)
!= getRequestedConfigurationOrientation(false /*forDisplay */)) {
// Do not change the requested configuration now, because this will be done when setting
- // the orientation below with the new mCompatDisplayInsets
+ // the orientation below with the new mAppCompatDisplayInsets
clearSizeCompatModeAttributes();
}
ProtoLog.v(WM_DEBUG_ORIENTATION,
@@ -8207,19 +8204,19 @@
}
@Nullable
- CompatDisplayInsets getCompatDisplayInsets() {
+ AppCompatDisplayInsets getAppCompatDisplayInsets() {
if (mAppCompatController.getTransparentPolicy().isRunning()) {
- return mAppCompatController.getTransparentPolicy().getInheritedCompatDisplayInsets();
+ return mAppCompatController.getTransparentPolicy().getInheritedAppCompatDisplayInsets();
}
- return mCompatDisplayInsets;
+ return mAppCompatDisplayInsets;
}
/**
- * @return The {@code true} if the current instance has {@link mCompatDisplayInsets} without
- * considering the inheritance implemented in {@link #getCompatDisplayInsets()}
+ * @return The {@code true} if the current instance has {@link mAppCompatDisplayInsets} without
+ * considering the inheritance implemented in {@link #getAppCompatDisplayInsets()}
*/
- boolean hasCompatDisplayInsetsWithoutInheritance() {
- return mCompatDisplayInsets != null;
+ boolean hasAppCompatDisplayInsetsWithoutInheritance() {
+ return mAppCompatDisplayInsets != null;
}
/**
@@ -8230,7 +8227,7 @@
if (mInSizeCompatModeForBounds) {
return true;
}
- if (getCompatDisplayInsets() == null || !shouldCreateCompatDisplayInsets()
+ if (getAppCompatDisplayInsets() == null || !shouldCreateAppCompatDisplayInsets()
// The orientation is different from parent when transforming.
|| isFixedRotationTransforming()) {
return false;
@@ -8256,13 +8253,13 @@
* Indicates the activity will keep the bounds and screen configuration when it was first
* launched, no matter how its parent changes.
*
- * <p>If {@true}, then {@link CompatDisplayInsets} will be created in {@link
+ * <p>If {@true}, then {@link AppCompatDisplayInsets} will be created in {@link
* #resolveOverrideConfiguration} to "freeze" activity bounds and insets.
*
* @return {@code true} if this activity is declared as non-resizable and fixed orientation or
* aspect ratio.
*/
- boolean shouldCreateCompatDisplayInsets() {
+ boolean shouldCreateAppCompatDisplayInsets() {
if (mAppCompatController.getAppCompatAspectRatioOverrides().hasFullscreenOverride()) {
// If the user has forced the applications aspect ratio to be fullscreen, don't use size
// compatibility mode in any situation. The user has been warned and therefore accepts
@@ -8284,7 +8281,7 @@
final TaskDisplayArea tda = getTaskDisplayArea();
if (inMultiWindowMode() || (tda != null && tda.inFreeformWindowingMode())) {
final ActivityRecord root = task != null ? task.getRootActivity() : null;
- if (root != null && root != this && !root.shouldCreateCompatDisplayInsets()) {
+ if (root != null && root != this && !root.shouldCreateAppCompatDisplayInsets()) {
// If the root activity doesn't use size compatibility mode, the activities above
// are forced to be the same for consistent visual appearance.
return false;
@@ -8328,8 +8325,8 @@
}
// TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
- private void updateCompatDisplayInsets() {
- if (getCompatDisplayInsets() != null || !shouldCreateCompatDisplayInsets()) {
+ private void updateAppCompatDisplayInsets() {
+ if (getAppCompatDisplayInsets() != null || !shouldCreateAppCompatDisplayInsets()) {
// The override configuration is set only once in size compatibility mode.
return;
}
@@ -8357,9 +8354,9 @@
final Rect letterboxedContainerBounds = mAppCompatController
.getAppCompatAspectRatioPolicy().getLetterboxedContainerBounds();
- // The role of CompatDisplayInsets is like the override bounds.
- mCompatDisplayInsets =
- new CompatDisplayInsets(
+ // The role of AppCompatDisplayInsets is like the override bounds.
+ mAppCompatDisplayInsets =
+ new AppCompatDisplayInsets(
mDisplayContent, this, letterboxedContainerBounds,
mResolveConfigHint.mUseOverrideInsetsForConfig);
}
@@ -8372,14 +8369,14 @@
forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
}
mSizeCompatBounds = null;
- mCompatDisplayInsets = null;
- mAppCompatController.getTransparentPolicy().clearInheritedCompatDisplayInsets();
+ mAppCompatDisplayInsets = null;
+ mAppCompatController.getTransparentPolicy().clearInheritedAppCompatDisplayInsets();
}
@VisibleForTesting
void clearSizeCompatMode() {
clearSizeCompatModeAttributes();
- // Clear config override in #updateCompatDisplayInsets().
+ // Clear config override in #updateAppCompatDisplayInsets().
final int activityType = getActivityType();
final Configuration overrideConfig = getRequestedOverrideConfiguration();
overrideConfig.unset();
@@ -8471,9 +8468,9 @@
.hasFullscreenOverride()) {
resolveAspectRatioRestriction(newParentConfiguration);
}
- final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();
- if (compatDisplayInsets != null) {
- resolveSizeCompatModeConfiguration(newParentConfiguration, compatDisplayInsets);
+ final AppCompatDisplayInsets appCompatDisplayInsets = getAppCompatDisplayInsets();
+ if (appCompatDisplayInsets != null) {
+ resolveSizeCompatModeConfiguration(newParentConfiguration, appCompatDisplayInsets);
} else if (inMultiWindowMode() && !isFixedOrientationLetterboxAllowed) {
// We ignore activities' requested orientation in multi-window modes. They may be
// taken into consideration in resolveFixedOrientationConfiguration call above.
@@ -8497,7 +8494,7 @@
resolveAspectRatioRestriction(newParentConfiguration);
}
- if (isFixedOrientationLetterboxAllowed || compatDisplayInsets != null
+ if (isFixedOrientationLetterboxAllowed || mAppCompatDisplayInsets != null
// In fullscreen, can be letterboxed for aspect ratio.
|| !inMultiWindowMode()) {
updateResolvedBoundsPosition(newParentConfiguration);
@@ -8505,8 +8502,8 @@
boolean isIgnoreOrientationRequest = mDisplayContent != null
&& mDisplayContent.getIgnoreOrientationRequest();
- if (compatDisplayInsets == null
- // for size compat mode set in updateCompatDisplayInsets
+ if (mAppCompatDisplayInsets == null
+ // for size compat mode set in updateAppCompatDisplayInsets
// Fixed orientation letterboxing is possible on both large screen devices
// with ignoreOrientationRequest enabled and on phones in split screen even with
// ignoreOrientationRequest disabled.
@@ -8533,7 +8530,7 @@
getResolvedOverrideConfiguration().seq = mConfigurationSeq;
// Sandbox max bounds by setting it to the activity bounds, if activity is letterboxed, or
- // has or will have mCompatDisplayInsets for size compat. Also forces an activity to be
+ // has or will have mAppCompatDisplayInsets for size compat. Also forces an activity to be
// sandboxed or not depending upon the configuration settings.
if (providesMaxBounds()) {
mTmpBounds.set(resolvedConfig.windowConfiguration.getBounds());
@@ -8554,8 +8551,8 @@
info.neverSandboxDisplayApis(sConstrainDisplayApisConfig),
info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig),
!matchParentBounds(),
- compatDisplayInsets != null,
- shouldCreateCompatDisplayInsets());
+ mAppCompatDisplayInsets != null,
+ shouldCreateAppCompatDisplayInsets());
}
resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
}
@@ -8567,7 +8564,7 @@
resolvedConfig,
mOptOutEdgeToEdge,
hasFixedRotationTransform(),
- getCompatDisplayInsets() != null,
+ getAppCompatDisplayInsets() != null,
task);
mResolveConfigHint.resetTmpOverrides();
@@ -8697,7 +8694,7 @@
offsetX = Math.max(0, (int) Math.ceil((appWidth
- screenResolvedBoundsWidth) * positionMultiplier)
// This is added to make sure that insets added inside
- // CompatDisplayInsets#getContainerBounds() do not break the alignment
+ // AppCompatDisplayInsets#getContainerBounds() do not break the alignment
// provided by the positionMultiplier
- screenResolvedBounds.left + parentAppBounds.left);
}
@@ -8718,7 +8715,7 @@
offsetY = Math.max(0, (int) Math.ceil((appHeight
- screenResolvedBoundsHeight) * positionMultiplier)
// This is added to make sure that insets added inside
- // CompatDisplayInsets#getContainerBounds() do not break the alignment
+ // AppCompatDisplayInsets#getContainerBounds() do not break the alignment
// provided by the positionMultiplier
- screenResolvedBounds.top + parentAppBounds.top);
}
@@ -8929,10 +8926,10 @@
|| orientationRespectedWithInsets)) {
return;
}
- final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();
+ final AppCompatDisplayInsets mAppCompatDisplayInsets = getAppCompatDisplayInsets();
- if (compatDisplayInsets != null
- && !compatDisplayInsets.mIsInFixedOrientationOrAspectRatioLetterbox) {
+ if (mAppCompatDisplayInsets != null
+ && !mAppCompatDisplayInsets.mIsInFixedOrientationOrAspectRatioLetterbox) {
// App prefers to keep its original size.
// If the size compat is from previous fixed orientation letterboxing, we may want to
// have fixed orientation letterbox again, otherwise it will show the size compat
@@ -8984,8 +8981,8 @@
.applyDesiredAspectRatio(newParentConfig, parentBounds, resolvedBounds,
containingBoundsWithInsets, containingBounds);
- if (compatDisplayInsets != null) {
- compatDisplayInsets.getBoundsByRotation(mTmpBounds,
+ if (mAppCompatDisplayInsets != null) {
+ mAppCompatDisplayInsets.getBoundsByRotation(mTmpBounds,
newParentConfig.windowConfiguration.getRotation());
if (resolvedBounds.width() != mTmpBounds.width()
|| resolvedBounds.height() != mTmpBounds.height()) {
@@ -9008,7 +9005,7 @@
// Calculate app bounds using fixed orientation bounds because they will be needed later
// for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
- mResolveConfigHint.mTmpCompatInsets = compatDisplayInsets;
+ mResolveConfigHint.mTmpCompatInsets = mAppCompatDisplayInsets;
computeConfigByResolveHint(getResolvedOverrideConfiguration(), newParentConfig);
mAppCompatController.getAppCompatAspectRatioPolicy()
.setLetterboxBoundsForFixedOrientationAndAspectRatio(new Rect(resolvedBounds));
@@ -9050,7 +9047,7 @@
* inheriting the parent bounds.
*/
private void resolveSizeCompatModeConfiguration(Configuration newParentConfiguration,
- @NonNull CompatDisplayInsets compatDisplayInsets) {
+ @NonNull AppCompatDisplayInsets appCompatDisplayInsets) {
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
@@ -9079,13 +9076,13 @@
? requestedOrientation
// We should use the original orientation of the activity when possible to avoid
// forcing the activity in the opposite orientation.
- : compatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
- ? compatDisplayInsets.mOriginalRequestedOrientation
+ : appCompatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
+ ? appCompatDisplayInsets.mOriginalRequestedOrientation
: parentOrientation;
int rotation = newParentConfiguration.windowConfiguration.getRotation();
final boolean isFixedToUserRotation = mDisplayContent == null
|| mDisplayContent.getDisplayRotation().isFixedToUserRotation();
- if (!isFixedToUserRotation && !compatDisplayInsets.mIsFloating) {
+ if (!isFixedToUserRotation && !appCompatDisplayInsets.mIsFloating) {
// Use parent rotation because the original display can be rotated.
resolvedConfig.windowConfiguration.setRotation(rotation);
} else {
@@ -9101,11 +9098,11 @@
// rely on them to contain the original and unchanging width and height of the app.
final Rect containingAppBounds = new Rect();
final Rect containingBounds = mTmpBounds;
- compatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation,
+ appCompatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation,
orientation, orientationRequested, isFixedToUserRotation);
resolvedBounds.set(containingBounds);
// The size of floating task is fixed (only swap), so the aspect ratio is already correct.
- if (!compatDisplayInsets.mIsFloating) {
+ if (!appCompatDisplayInsets.mIsFloating) {
mAppCompatController.getAppCompatAspectRatioPolicy()
.applyAspectRatioForLetterbox(resolvedBounds, containingAppBounds,
containingBounds);
@@ -9114,7 +9111,7 @@
// Use resolvedBounds to compute other override configurations such as appBounds. The bounds
// are calculated in compat container space. The actual position on screen will be applied
// later, so the calculation is simpler that doesn't need to involve offset from parent.
- mResolveConfigHint.mTmpCompatInsets = compatDisplayInsets;
+ mResolveConfigHint.mTmpCompatInsets = appCompatDisplayInsets;
computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
// Use current screen layout as source because the size of app is independent to parent.
resolvedConfig.screenLayout = computeScreenLayout(
@@ -9306,13 +9303,13 @@
if (info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig)) {
return true;
}
- // Max bounds should be sandboxed when an activity should have compatDisplayInsets, and it
- // will keep the same bounds and screen configuration when it was first launched regardless
- // how its parent window changes, so that the sandbox API will provide a consistent result.
- if (getCompatDisplayInsets() != null || shouldCreateCompatDisplayInsets()) {
+ // Max bounds should be sandboxed when an activity should have mAppCompatDisplayInsets,
+ // and it will keep the same bounds and screen configuration when it was first launched
+ // regardless how its parent window changes, so that the sandbox API will provide a
+ // consistent result.
+ if (getAppCompatDisplayInsets() != null || shouldCreateAppCompatDisplayInsets()) {
return true;
}
-
// No need to sandbox for resizable apps in (including in multi-window) because
// resizableActivity=true indicates that they support multi-window. Likewise, do not sandbox
// for activities in letterbox since the activity has declared it can handle resizing.
@@ -9363,7 +9360,7 @@
mTransitionController.collect(this);
}
}
- if (getCompatDisplayInsets() != null) {
+ if (getAppCompatDisplayInsets() != null) {
Configuration overrideConfig = getRequestedOverrideConfiguration();
// Adapt to changes in orientation locking. The app is still non-resizable, but
// it can change which orientation is fixed. If the fixed orientation changes,
@@ -9443,9 +9440,9 @@
if (mVisibleRequested) {
// It may toggle the UI for user to restart the size compatibility mode activity.
display.handleActivitySizeCompatModeIfNeeded(this);
- } else if (getCompatDisplayInsets() != null && !visibleIgnoringKeyguard
+ } else if (getAppCompatDisplayInsets() != null && !visibleIgnoringKeyguard
&& (app == null || !app.hasVisibleActivities())) {
- // visibleIgnoringKeyguard is checked to avoid clearing mCompatDisplayInsets during
+ // visibleIgnoringKeyguard is checked to avoid clearing mAppCompatDisplayInsets during
// displays change. Displays are turned off during the change so mVisibleRequested
// can be false.
// The override changes can only be obtained from display, because we don't have the
@@ -9608,14 +9605,14 @@
// Calling from here rather than from onConfigurationChanged because it's possible that
// onConfigurationChanged was called before mVisibleRequested became true and
- // mCompatDisplayInsets may not be called again when mVisibleRequested changes. And we
- // don't want to save mCompatDisplayInsets in onConfigurationChanged without visibility
+ // mAppCompatDisplayInsets may not be called again when mVisibleRequested changes. And we
+ // don't want to save mAppCompatDisplayInsets in onConfigurationChanged without visibility
// check to avoid remembering obsolete configuration which can lead to unnecessary
// size-compat mode.
if (mVisibleRequested) {
// Calling from here rather than resolveOverrideConfiguration to ensure that this is
// called after full config is updated in ConfigurationContainer#onConfigurationChanged.
- updateCompatDisplayInsets();
+ updateAppCompatDisplayInsets();
}
// Short circuit: if the two full configurations are equal (the common case), then there is
@@ -10441,202 +10438,6 @@
proto.end(token);
}
- /**
- * The precomputed insets of the display in each rotation. This is used to make the size
- * compatibility mode activity compute the configuration without relying on its current display.
- */
- static class CompatDisplayInsets {
- /** The original rotation the compat insets were computed in. */
- final @Rotation int mOriginalRotation;
- /** The original requested orientation for the activity. */
- final @Configuration.Orientation int mOriginalRequestedOrientation;
- /** The container width on rotation 0. */
- private final int mWidth;
- /** The container height on rotation 0. */
- private final int mHeight;
- /** Whether the {@link Task} windowingMode represents a floating window*/
- final boolean mIsFloating;
- /**
- * Whether is letterboxed because of fixed orientation or aspect ratio when
- * the unresizable activity is first shown.
- */
- final boolean mIsInFixedOrientationOrAspectRatioLetterbox;
- /**
- * The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. It
- * is used to compute the appBounds.
- */
- final Rect[] mNonDecorInsets = new Rect[4];
- /**
- * The stableInsets for each rotation. Includes the status bar inset and the
- * nonDecorInsets. It is used to compute {@link Configuration#screenWidthDp} and
- * {@link Configuration#screenHeightDp}.
- */
- final Rect[] mStableInsets = new Rect[4];
-
- /** Constructs the environment to simulate the bounds behavior of the given container. */
- CompatDisplayInsets(DisplayContent display, ActivityRecord container,
- @Nullable Rect letterboxedContainerBounds, boolean useOverrideInsets) {
- mOriginalRotation = display.getRotation();
- mIsFloating = container.getWindowConfiguration().tasksAreFloating();
- mOriginalRequestedOrientation = container.getRequestedConfigurationOrientation();
- if (mIsFloating) {
- final Rect containerBounds = container.getWindowConfiguration().getBounds();
- mWidth = containerBounds.width();
- mHeight = containerBounds.height();
- // For apps in freeform, the task bounds are the parent bounds from the app's
- // perspective. No insets because within a window.
- final Rect emptyRect = new Rect();
- for (int rotation = 0; rotation < 4; rotation++) {
- mNonDecorInsets[rotation] = emptyRect;
- mStableInsets[rotation] = emptyRect;
- }
- mIsInFixedOrientationOrAspectRatioLetterbox = false;
- return;
- }
-
- final Task task = container.getTask();
-
- mIsInFixedOrientationOrAspectRatioLetterbox = letterboxedContainerBounds != null;
-
- // Store the bounds of the Task for the non-resizable activity to use in size compat
- // mode so that the activity will not be resized regardless the windowing mode it is
- // currently in.
- // When an activity needs to be letterboxed because of fixed orientation or aspect
- // ratio, use resolved bounds instead of task bounds since the activity will be
- // displayed within these even if it is in size compat mode.
- final Rect filledContainerBounds = mIsInFixedOrientationOrAspectRatioLetterbox
- ? letterboxedContainerBounds
- : task != null ? task.getBounds() : display.getBounds();
- final boolean useActivityRotation = container.hasFixedRotationTransform()
- && mIsInFixedOrientationOrAspectRatioLetterbox;
- final int filledContainerRotation = useActivityRotation
- ? container.getWindowConfiguration().getRotation()
- : display.getConfiguration().windowConfiguration.getRotation();
- final Point dimensions = getRotationZeroDimensions(
- filledContainerBounds, filledContainerRotation);
- mWidth = dimensions.x;
- mHeight = dimensions.y;
-
- // Bounds of the filled container if it doesn't fill the display.
- final Rect unfilledContainerBounds =
- filledContainerBounds.equals(display.getBounds()) ? null : new Rect();
- final DisplayPolicy policy = display.getDisplayPolicy();
- for (int rotation = 0; rotation < 4; rotation++) {
- mNonDecorInsets[rotation] = new Rect();
- mStableInsets[rotation] = new Rect();
- final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- final int dw = rotated ? display.mBaseDisplayHeight : display.mBaseDisplayWidth;
- final int dh = rotated ? display.mBaseDisplayWidth : display.mBaseDisplayHeight;
- final DisplayPolicy.DecorInsets.Info decorInfo =
- policy.getDecorInsetsInfo(rotation, dw, dh);
- if (useOverrideInsets) {
- mStableInsets[rotation].set(decorInfo.mOverrideConfigInsets);
- mNonDecorInsets[rotation].set(decorInfo.mOverrideNonDecorInsets);
- } else {
- mStableInsets[rotation].set(decorInfo.mConfigInsets);
- mNonDecorInsets[rotation].set(decorInfo.mNonDecorInsets);
- }
-
- if (unfilledContainerBounds == null) {
- continue;
- }
- // The insets is based on the display, but the container may be smaller than the
- // display, so update the insets to exclude parts that are not intersected with the
- // container.
- unfilledContainerBounds.set(filledContainerBounds);
- display.rotateBounds(
- filledContainerRotation,
- rotation,
- unfilledContainerBounds);
- updateInsetsForBounds(unfilledContainerBounds, dw, dh, mNonDecorInsets[rotation]);
- updateInsetsForBounds(unfilledContainerBounds, dw, dh, mStableInsets[rotation]);
- }
- }
-
- /**
- * Gets the width and height of the {@code container} when it is not rotated, so that after
- * the display is rotated, we can calculate the bounds by rotating the dimensions.
- * @see #getBoundsByRotation
- */
- private static Point getRotationZeroDimensions(final Rect bounds, int rotation) {
- final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- final int width = bounds.width();
- final int height = bounds.height();
- return rotated ? new Point(height, width) : new Point(width, height);
- }
-
- /**
- * Updates the display insets to exclude the parts that are not intersected with the given
- * bounds.
- */
- private static void updateInsetsForBounds(Rect bounds, int displayWidth, int displayHeight,
- Rect inset) {
- inset.left = Math.max(0, inset.left - bounds.left);
- inset.top = Math.max(0, inset.top - bounds.top);
- inset.right = Math.max(0, bounds.right - displayWidth + inset.right);
- inset.bottom = Math.max(0, bounds.bottom - displayHeight + inset.bottom);
- }
-
- void getBoundsByRotation(Rect outBounds, int rotation) {
- final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- final int dw = rotated ? mHeight : mWidth;
- final int dh = rotated ? mWidth : mHeight;
- outBounds.set(0, 0, dw, dh);
- }
-
- void getFrameByOrientation(Rect outBounds, int orientation) {
- final int longSide = Math.max(mWidth, mHeight);
- final int shortSide = Math.min(mWidth, mHeight);
- final boolean isLandscape = orientation == ORIENTATION_LANDSCAPE;
- outBounds.set(0, 0, isLandscape ? longSide : shortSide,
- isLandscape ? shortSide : longSide);
- }
-
- // TODO(b/267151420): Explore removing getContainerBounds() from CompatDisplayInsets.
- /** Gets the horizontal centered container bounds for size compatibility mode. */
- void getContainerBounds(Rect outAppBounds, Rect outBounds, int rotation, int orientation,
- boolean orientationRequested, boolean isFixedToUserRotation) {
- getFrameByOrientation(outBounds, orientation);
- if (mIsFloating) {
- outAppBounds.set(outBounds);
- return;
- }
-
- getBoundsByRotation(outAppBounds, rotation);
- final int dW = outAppBounds.width();
- final int dH = outAppBounds.height();
- final boolean isOrientationMismatched =
- ((outBounds.width() > outBounds.height()) != (dW > dH));
-
- if (isOrientationMismatched && isFixedToUserRotation && orientationRequested) {
- // The orientation is mismatched but the display cannot rotate. The bounds will fit
- // to the short side of container.
- if (orientation == ORIENTATION_LANDSCAPE) {
- outBounds.bottom = (int) ((float) dW * dW / dH);
- outBounds.right = dW;
- } else {
- outBounds.bottom = dH;
- outBounds.right = (int) ((float) dH * dH / dW);
- }
- outBounds.offset(getCenterOffset(mWidth, outBounds.width()), 0 /* dy */);
- }
- outAppBounds.set(outBounds);
-
- if (isOrientationMismatched) {
- // One side of container is smaller than the requested size, then it will be scaled
- // and the final position will be calculated according to the parent container and
- // scale, so the original size shouldn't be shrunk by insets.
- final Rect insets = mNonDecorInsets[rotation];
- outBounds.offset(insets.left, insets.top);
- outAppBounds.offset(insets.left, insets.top);
- } else if (rotation != ROTATION_UNDEFINED) {
- // Ensure the app bounds won't overlap with insets.
- TaskFragment.intersectWithInsetsIfFits(outAppBounds, outBounds,
- mNonDecorInsets[rotation]);
- }
- }
- }
-
private static class AppSaturationInfo {
float[] mMatrix = new float[9];
float[] mTranslation = new float[3];
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index cd795ae..f245efd 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -220,7 +220,7 @@
float getFixedOrientationLetterboxAspectRatio(@NonNull Configuration parentConfiguration) {
return shouldUseSplitScreenAspectRatio(parentConfiguration)
? getSplitScreenAspectRatio()
- : mActivityRecord.shouldCreateCompatDisplayInsets()
+ : mActivityRecord.shouldCreateAppCompatDisplayInsets()
? getDefaultMinAspectRatioForUnresizableApps()
: getDefaultMinAspectRatio();
}
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
index aeaaffd..d8abf69 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
@@ -217,7 +217,7 @@
*/
boolean isCameraCompatSplitScreenAspectRatioAllowed() {
return mAppCompatConfiguration.isCameraCompatSplitScreenAspectRatioEnabled()
- && !mActivityRecord.shouldCreateCompatDisplayInsets();
+ && !mActivityRecord.shouldCreateAppCompatDisplayInsets();
}
@FreeformCameraCompatMode
diff --git a/services/core/java/com/android/server/wm/AppCompatDisplayInsets.java b/services/core/java/com/android/server/wm/AppCompatDisplayInsets.java
new file mode 100644
index 0000000..743bfb9
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatDisplayInsets.java
@@ -0,0 +1,234 @@
+/*
+ * 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.wm;
+
+
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Surface;
+
+/**
+ * The precomputed insets of the display in each rotation. This is used to make the size
+ * compatibility mode activity compute the configuration without relying on its current display.
+ */
+class AppCompatDisplayInsets {
+ /** The original rotation the compat insets were computed in. */
+ final @Surface.Rotation int mOriginalRotation;
+ /** The original requested orientation for the activity. */
+ final @Configuration.Orientation int mOriginalRequestedOrientation;
+ /** The container width on rotation 0. */
+ private final int mWidth;
+ /** The container height on rotation 0. */
+ private final int mHeight;
+ /** Whether the {@link Task} windowingMode represents a floating window*/
+ final boolean mIsFloating;
+ /**
+ * Whether is letterboxed because of fixed orientation or aspect ratio when
+ * the unresizable activity is first shown.
+ */
+ final boolean mIsInFixedOrientationOrAspectRatioLetterbox;
+ /**
+ * The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. It
+ * is used to compute the appBounds.
+ */
+ final Rect[] mNonDecorInsets = new Rect[4];
+ /**
+ * The stableInsets for each rotation. Includes the status bar inset and the
+ * nonDecorInsets. It is used to compute {@link Configuration#screenWidthDp} and
+ * {@link Configuration#screenHeightDp}.
+ */
+ final Rect[] mStableInsets = new Rect[4];
+
+ /** Constructs the environment to simulate the bounds behavior of the given container. */
+ AppCompatDisplayInsets(@NonNull DisplayContent display, @NonNull ActivityRecord container,
+ @Nullable Rect letterboxedContainerBounds, boolean useOverrideInsets) {
+ mOriginalRotation = display.getRotation();
+ mIsFloating = container.getWindowConfiguration().tasksAreFloating();
+ mOriginalRequestedOrientation = container.getRequestedConfigurationOrientation();
+ if (mIsFloating) {
+ final Rect containerBounds = container.getWindowConfiguration().getBounds();
+ mWidth = containerBounds.width();
+ mHeight = containerBounds.height();
+ // For apps in freeform, the task bounds are the parent bounds from the app's
+ // perspective. No insets because within a window.
+ final Rect emptyRect = new Rect();
+ for (int rotation = 0; rotation < 4; rotation++) {
+ mNonDecorInsets[rotation] = emptyRect;
+ mStableInsets[rotation] = emptyRect;
+ }
+ mIsInFixedOrientationOrAspectRatioLetterbox = false;
+ return;
+ }
+
+ final Task task = container.getTask();
+
+ mIsInFixedOrientationOrAspectRatioLetterbox = letterboxedContainerBounds != null;
+
+ // Store the bounds of the Task for the non-resizable activity to use in size compat
+ // mode so that the activity will not be resized regardless the windowing mode it is
+ // currently in.
+ // When an activity needs to be letterboxed because of fixed orientation or aspect
+ // ratio, use resolved bounds instead of task bounds since the activity will be
+ // displayed within these even if it is in size compat mode.
+ final Rect filledContainerBounds = mIsInFixedOrientationOrAspectRatioLetterbox
+ ? letterboxedContainerBounds
+ : task != null ? task.getBounds() : display.getBounds();
+ final boolean useActivityRotation = container.hasFixedRotationTransform()
+ && mIsInFixedOrientationOrAspectRatioLetterbox;
+ final int filledContainerRotation = useActivityRotation
+ ? container.getWindowConfiguration().getRotation()
+ : display.getConfiguration().windowConfiguration.getRotation();
+ final Point dimensions = getRotationZeroDimensions(
+ filledContainerBounds, filledContainerRotation);
+ mWidth = dimensions.x;
+ mHeight = dimensions.y;
+
+ // Bounds of the filled container if it doesn't fill the display.
+ final Rect unfilledContainerBounds =
+ filledContainerBounds.equals(display.getBounds()) ? null : new Rect();
+ final DisplayPolicy policy = display.getDisplayPolicy();
+ for (int rotation = 0; rotation < 4; rotation++) {
+ mNonDecorInsets[rotation] = new Rect();
+ mStableInsets[rotation] = new Rect();
+ final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+ final int dw = rotated ? display.mBaseDisplayHeight : display.mBaseDisplayWidth;
+ final int dh = rotated ? display.mBaseDisplayWidth : display.mBaseDisplayHeight;
+ final DisplayPolicy.DecorInsets.Info decorInfo =
+ policy.getDecorInsetsInfo(rotation, dw, dh);
+ if (useOverrideInsets) {
+ mStableInsets[rotation].set(decorInfo.mOverrideConfigInsets);
+ mNonDecorInsets[rotation].set(decorInfo.mOverrideNonDecorInsets);
+ } else {
+ mStableInsets[rotation].set(decorInfo.mConfigInsets);
+ mNonDecorInsets[rotation].set(decorInfo.mNonDecorInsets);
+ }
+
+ if (unfilledContainerBounds == null) {
+ continue;
+ }
+ // The insets is based on the display, but the container may be smaller than the
+ // display, so update the insets to exclude parts that are not intersected with the
+ // container.
+ unfilledContainerBounds.set(filledContainerBounds);
+ display.rotateBounds(
+ filledContainerRotation,
+ rotation,
+ unfilledContainerBounds);
+ updateInsetsForBounds(unfilledContainerBounds, dw, dh, mNonDecorInsets[rotation]);
+ updateInsetsForBounds(unfilledContainerBounds, dw, dh, mStableInsets[rotation]);
+ }
+ }
+
+ /**
+ * Gets the width and height of the {@code container} when it is not rotated, so that after
+ * the display is rotated, we can calculate the bounds by rotating the dimensions.
+ * @see #getBoundsByRotation
+ */
+ @NonNull
+ private static Point getRotationZeroDimensions(final @NonNull Rect bounds,
+ @Surface.Rotation int rotation) {
+ final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+ final int width = bounds.width();
+ final int height = bounds.height();
+ return rotated ? new Point(height, width) : new Point(width, height);
+ }
+
+ /**
+ * Updates the display insets to exclude the parts that are not intersected with the given
+ * bounds.
+ */
+ private static void updateInsetsForBounds(@NonNull Rect bounds, int displayWidth,
+ int displayHeight, @NonNull Rect inset) {
+ inset.left = Math.max(0, inset.left - bounds.left);
+ inset.top = Math.max(0, inset.top - bounds.top);
+ inset.right = Math.max(0, bounds.right - displayWidth + inset.right);
+ inset.bottom = Math.max(0, bounds.bottom - displayHeight + inset.bottom);
+ }
+
+ void getBoundsByRotation(@NonNull Rect outBounds, @Surface.Rotation int rotation) {
+ final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+ final int dw = rotated ? mHeight : mWidth;
+ final int dh = rotated ? mWidth : mHeight;
+ outBounds.set(0, 0, dw, dh);
+ }
+
+ void getFrameByOrientation(@NonNull Rect outBounds,
+ @Configuration.Orientation int orientation) {
+ final int longSide = Math.max(mWidth, mHeight);
+ final int shortSide = Math.min(mWidth, mHeight);
+ final boolean isLandscape = orientation == ORIENTATION_LANDSCAPE;
+ outBounds.set(0, 0, isLandscape ? longSide : shortSide,
+ isLandscape ? shortSide : longSide);
+ }
+
+ /** Gets the horizontal centered container bounds for size compatibility mode. */
+ void getContainerBounds(@NonNull Rect outAppBounds, @NonNull Rect outBounds,
+ @Surface.Rotation int rotation, @Configuration.Orientation int orientation,
+ boolean orientationRequested, boolean isFixedToUserRotation) {
+ getFrameByOrientation(outBounds, orientation);
+ if (mIsFloating) {
+ outAppBounds.set(outBounds);
+ return;
+ }
+
+ getBoundsByRotation(outAppBounds, rotation);
+ final int dW = outAppBounds.width();
+ final int dH = outAppBounds.height();
+ final boolean isOrientationMismatched =
+ ((outBounds.width() > outBounds.height()) != (dW > dH));
+
+ if (isOrientationMismatched && isFixedToUserRotation && orientationRequested) {
+ // The orientation is mismatched but the display cannot rotate. The bounds will fit
+ // to the short side of container.
+ if (orientation == ORIENTATION_LANDSCAPE) {
+ outBounds.bottom = (int) ((float) dW * dW / dH);
+ outBounds.right = dW;
+ } else {
+ outBounds.bottom = dH;
+ outBounds.right = (int) ((float) dH * dH / dW);
+ }
+ outBounds.offset(getCenterOffset(mWidth, outBounds.width()), 0 /* dy */);
+ }
+ outAppBounds.set(outBounds);
+
+ if (isOrientationMismatched) {
+ // One side of container is smaller than the requested size, then it will be scaled
+ // and the final position will be calculated according to the parent container and
+ // scale, so the original size shouldn't be shrunk by insets.
+ final Rect insets = mNonDecorInsets[rotation];
+ outBounds.offset(insets.left, insets.top);
+ outAppBounds.offset(insets.left, insets.top);
+ } else if (rotation != ROTATION_UNDEFINED) {
+ // Ensure the app bounds won't overlap with insets.
+ TaskFragment.intersectWithInsetsIfFits(outAppBounds, outBounds,
+ mNonDecorInsets[rotation]);
+ }
+ }
+
+ /** @return The horizontal / vertical offset of putting the content in the center of viewport.*/
+ private static int getCenterOffset(int viewportDim, int contentDim) {
+ return (int) ((viewportDim - contentDim + 1) * 0.5f);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
index b936556..ff1742b 100644
--- a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
@@ -104,7 +104,7 @@
* resizability.
*/
private float getFixedOrientationLetterboxAspectRatio(@NonNull Task task) {
- return mActivityRecord.shouldCreateCompatDisplayInsets()
+ return mActivityRecord.shouldCreateAppCompatDisplayInsets()
? getDefaultMinAspectRatioForUnresizableApps(task)
: getDefaultMinAspectRatio(task);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 08b1e37..13d6ed5e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1285,8 +1285,8 @@
EventLogTags.writeWmTaskMoved(mTaskId, getRootTaskId(), getDisplayId(), toTop ? 1 : 0,
position);
final TaskDisplayArea taskDisplayArea = getDisplayArea();
- if (taskDisplayArea != null && isLeafTask()) {
- taskDisplayArea.onLeafTaskMoved(this, toTop, toBottom);
+ if (taskDisplayArea != null) {
+ taskDisplayArea.onTaskMoved(this, toTop, toBottom);
}
if (isPersistable) {
mLastTimeMoved = System.currentTimeMillis();
@@ -6141,9 +6141,7 @@
if (canBeLaunchedOnDisplay(newParent.getDisplayId())) {
reparent(newParent, onTop ? POSITION_TOP : POSITION_BOTTOM);
- if (isLeafTask()) {
- newParent.onLeafTaskMoved(this, onTop, !onTop);
- }
+ newParent.onTaskMoved(this, onTop, !onTop);
} else {
Slog.w(TAG, "Task=" + this + " can't reparent to " + newParent);
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index d9e88e1..01fea47 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -37,6 +37,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.ColorInt;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.WindowConfiguration;
@@ -435,7 +436,19 @@
}
}
- void onLeafTaskMoved(Task t, boolean toTop, boolean toBottom) {
+ void onTaskMoved(@NonNull Task t, boolean toTop, boolean toBottom) {
+ if (toBottom && !t.isLeafTask()) {
+ // Return early when a non-leaf task moved to bottom, to prevent sending duplicated
+ // leaf task movement callback if the leaf task is moved along with its parent tasks.
+ // Unless, we also track the task id, like `mLastLeafTaskToFrontId`.
+ return;
+ }
+
+ final Task topLeafTask = t.getTopLeafTask();
+ onLeafTaskMoved(topLeafTask, toTop, toBottom);
+ }
+
+ void onLeafTaskMoved(@NonNull Task t, boolean toTop, boolean toBottom) {
if (toBottom) {
mAtmService.getTaskChangeNotificationController().notifyTaskMovedToBack(
t.getTaskInfo());
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 2fbabc5..c8139fa 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2224,7 +2224,7 @@
static class ConfigOverrideHint {
@Nullable DisplayInfo mTmpOverrideDisplayInfo;
- @Nullable ActivityRecord.CompatDisplayInsets mTmpCompatInsets;
+ @Nullable AppCompatDisplayInsets mTmpCompatInsets;
@Nullable Rect mParentAppBoundsOverride;
int mTmpOverrideConfigOrientation;
boolean mUseOverrideInsetsForConfig;
@@ -2294,7 +2294,7 @@
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig, @Nullable ConfigOverrideHint overrideHint) {
DisplayInfo overrideDisplayInfo = null;
- ActivityRecord.CompatDisplayInsets compatInsets = null;
+ AppCompatDisplayInsets compatInsets = null;
boolean useOverrideInsetsForConfig = false;
if (overrideHint != null) {
overrideDisplayInfo = overrideHint.mTmpOverrideDisplayInfo;
diff --git a/services/core/java/com/android/server/wm/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java
index f2615f7..016b65e 100644
--- a/services/core/java/com/android/server/wm/TransparentPolicy.java
+++ b/services/core/java/com/android/server/wm/TransparentPolicy.java
@@ -95,7 +95,7 @@
}
final boolean wasStarted = mTransparentPolicyState.isRunning();
mTransparentPolicyState.reset();
- // In case mActivityRecord.hasCompatDisplayInsetsWithoutOverride() we don't apply the
+ // In case mActivityRecord.hasAppCompatDisplayInsetsWithoutOverride() we don't apply the
// opaque activity constraints because we're expecting the activity is already letterboxed.
final ActivityRecord firstOpaqueActivity = mActivityRecord.getTask().getActivity(
FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */,
@@ -159,12 +159,12 @@
return mTransparentPolicyState.mInheritedOrientation;
}
- ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() {
- return mTransparentPolicyState.mInheritedCompatDisplayInsets;
+ AppCompatDisplayInsets getInheritedAppCompatDisplayInsets() {
+ return mTransparentPolicyState.mInheritedAppCompatDisplayInsets;
}
- void clearInheritedCompatDisplayInsets() {
- mTransparentPolicyState.clearInheritedCompatDisplayInsets();
+ void clearInheritedAppCompatDisplayInsets() {
+ mTransparentPolicyState.clearInheritedAppCompatDisplayInsets();
}
/**
@@ -202,7 +202,7 @@
return true;
}
if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent()
- || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) {
+ || mActivityRecord.hasAppCompatDisplayInsetsWithoutInheritance()) {
return true;
}
return false;
@@ -239,9 +239,9 @@
// The app compat state for the opaque activity if any
private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
- // The CompatDisplayInsets of the opaque activity beneath the translucent one.
+ // The AppCompatDisplayInsets of the opaque activity beneath the translucent one.
@Nullable
- private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets;
+ private AppCompatDisplayInsets mInheritedAppCompatDisplayInsets;
@Nullable
private ActivityRecord mFirstOpaqueActivity;
@@ -303,7 +303,7 @@
}
mInheritedOrientation = opaqueActivity.getRequestedConfigurationOrientation();
mInheritedAppCompatState = opaqueActivity.getAppCompatState();
- mInheritedCompatDisplayInsets = opaqueActivity.getCompatDisplayInsets();
+ mInheritedAppCompatDisplayInsets = opaqueActivity.getAppCompatDisplayInsets();
}
private void reset() {
@@ -315,7 +315,7 @@
mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO;
mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO;
mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
- mInheritedCompatDisplayInsets = null;
+ mInheritedAppCompatDisplayInsets = null;
if (mFirstOpaqueActivity != null) {
mFirstOpaqueActivity.mAppCompatController.getTransparentPolicy()
.mDestroyListeners.remove(mActivityRecord.mAppCompatController
@@ -340,8 +340,8 @@
|| !mActivityRecord.handlesOrientationChangeFromDescendant(orientation);
}
- private void clearInheritedCompatDisplayInsets() {
- mInheritedCompatDisplayInsets = null;
+ private void clearInheritedAppCompatDisplayInsets() {
+ mInheritedAppCompatDisplayInsets = null;
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 9ae881b..a980b77 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -2997,12 +2998,18 @@
// Make sure display isn't a part of the transition already - needed for legacy transitions.
if (mDisplayContent.inTransition()) return false;
- if (!ActivityTaskManagerService.isPip2ExperimentEnabled()) {
- // Screenshots are turned off when PiP is undergoing changes.
- return !inPinnedWindowingMode() && getParent() != null
- && !getParent().inPinnedWindowingMode();
- }
- return true;
+ // Screenshots are turned off when PiP is undergoing changes.
+ return ActivityTaskManagerService.isPip2ExperimentEnabled() || !isPipChange();
+ }
+
+ /** Returns true if WC is pinned and undergoing changes. */
+ private boolean isPipChange() {
+ final boolean isExitingPip = this.asTaskFragment() != null
+ && mTransitionController.getWindowingModeAtStart(this) == WINDOWING_MODE_PINNED
+ && !inPinnedWindowingMode();
+
+ return isExitingPip || inPinnedWindowingMode()
+ || (getParent() != null && getParent().inPinnedWindowingMode());
}
/**
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index bbf2ecb..026fcc4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -2080,6 +2080,31 @@
}
/**
+ * Tests that the DisplayInfo is updated correctly with a render frame rate even if it not
+ * a divisor of the peak refresh rate.
+ */
+ @Test
+ public void testDisplayInfoRenderFrameRateNonPeakDivisor() {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+ new float[]{120f}, new float[]{240f});
+ int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+ displayDevice);
+ DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(120f, displayInfo.getRefreshRate(), 0.01f);
+
+ updateRenderFrameRate(displayManager, displayDevice, 80f);
+ displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(80f, displayInfo.getRefreshRate(), 0.01f);
+ }
+
+ /**
* Tests that the mode reflects the render frame rate is in compat mode
*/
@Test
@@ -3348,13 +3373,26 @@
}
private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
-
float[] refreshRates) {
return createFakeDisplayDevice(displayManager, refreshRates, Display.TYPE_UNKNOWN);
}
private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
float[] refreshRates,
+ float[] vsyncRates) {
+ return createFakeDisplayDevice(displayManager, refreshRates, vsyncRates,
+ Display.TYPE_UNKNOWN);
+ }
+
+ private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
+ float[] refreshRates,
+ int displayType) {
+ return createFakeDisplayDevice(displayManager, refreshRates, refreshRates, displayType);
+ }
+
+ private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
+ float[] refreshRates,
+ float[] vsyncRates,
int displayType) {
FakeDisplayDevice displayDevice = new FakeDisplayDevice();
DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
@@ -3363,7 +3401,8 @@
displayDeviceInfo.supportedModes = new Display.Mode[refreshRates.length];
for (int i = 0; i < refreshRates.length; i++) {
displayDeviceInfo.supportedModes[i] =
- new Display.Mode(i + 1, width, height, refreshRates[i]);
+ new Display.Mode(i + 1, width, height, refreshRates[i], vsyncRates[i],
+ new float[0], new int[0]);
}
displayDeviceInfo.modeId = 1;
displayDeviceInfo.type = displayType;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 5840cb9..2166cb7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -2213,6 +2213,20 @@
/* ignoreAnimationLimits= */ anyBoolean());
}
+ @Test
+ public void testManualBrightnessModeSavesBrightness() {
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Initialize
+
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+ advanceTime(1);
+
+ verify(mHolder.brightnessSetting).saveIfNeeded();
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index e610a32..809e13c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -28,8 +28,8 @@
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
-import static android.os.PowerExemptionManager.REASON_DENIED;
import static android.content.ContentResolver.SCHEME_CONTENT;
+import static android.os.PowerExemptionManager.REASON_DENIED;
import static android.os.UserHandle.USER_ALL;
import static android.util.DebugUtils.valueToString;
@@ -68,11 +68,9 @@
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -84,7 +82,6 @@
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
import android.app.ForegroundServiceDelegationOptions;
-import android.app.IApplicationThread;
import android.app.IUidObserver;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -129,7 +126,7 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.sdksandbox.flags.Flags;
import com.android.server.LocalServices;
-import com.android.server.am.ActivityManagerService.StickyBroadcast;
+import com.android.server.am.BroadcastController.StickyBroadcast;
import com.android.server.am.ProcessList.IsolatedUidRange;
import com.android.server.am.ProcessList.IsolatedUidRangeAllocator;
import com.android.server.am.UidObserverController.ChangeRecord;
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 37d87c4e..1cba3c5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -52,6 +52,7 @@
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.multiuser.Flags;
import android.os.PowerManager;
import android.os.ServiceSpecificException;
@@ -152,6 +153,7 @@
private File mTestDir;
private Context mSpiedContext;
+ private Resources mSpyResources;
private @Mock PackageManagerService mMockPms;
private @Mock UserDataPreparer mMockUserDataPreparer;
@@ -193,6 +195,13 @@
doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
mockIsLowRamDevice(false);
+ // Called when getting boot user. config_bootToHeadlessSystemUser is false by default.
+ mSpyResources = spy(mSpiedContext.getResources());
+ when(mSpiedContext.getResources()).thenReturn(mSpyResources);
+ doReturn(false)
+ .when(mSpyResources)
+ .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser);
+
// Must construct UserManagerService in the UiThread
mTestDir = new File(mRealContext.getDataDir(), "umstest");
mTestDir.mkdirs();
@@ -849,6 +858,16 @@
USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
}
+ @Test
+ public void testGetBootUser_enableBootToHeadlessSystemUser() {
+ setSystemUserHeadless(true);
+ doReturn(true)
+ .when(mSpyResources)
+ .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser);
+
+ assertThat(mUms.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
+ }
+
/**
* Returns true if the user's XML file has Default restrictions
* @param userId Id of the user.
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
index bbab0ee..7b635d4 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
@@ -38,6 +38,7 @@
import android.hardware.power.stats.StateResidencyResult;
import android.os.Handler;
import android.os.Looper;
+import android.os.connectivity.WifiActivityEnergyInfo;
import android.platform.test.ravenwood.RavenwoodRule;
import android.power.PowerStatsInternal;
import android.util.IntArray;
@@ -88,6 +89,33 @@
}
@Test
+ public void testUpdateWifiState() {
+ WifiActivityEnergyInfo firstInfo = new WifiActivityEnergyInfo(1111,
+ WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 11, 22, 33, 44);
+
+ WifiActivityEnergyInfo delta = mBatteryExternalStatsWorker.extractDeltaLocked(firstInfo);
+
+ assertEquals(1111, delta.getTimeSinceBootMillis());
+ assertEquals(WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, delta.getStackState());
+ assertEquals(0, delta.getControllerTxDurationMillis());
+ assertEquals(0, delta.getControllerRxDurationMillis());
+ assertEquals(0, delta.getControllerScanDurationMillis());
+ assertEquals(0, delta.getControllerIdleDurationMillis());
+
+ WifiActivityEnergyInfo secondInfo = new WifiActivityEnergyInfo(91111,
+ WifiActivityEnergyInfo.STACK_STATE_STATE_IDLE, 811, 722, 633, 544);
+
+ delta = mBatteryExternalStatsWorker.extractDeltaLocked(secondInfo);
+
+ assertEquals(91111, delta.getTimeSinceBootMillis());
+ assertEquals(WifiActivityEnergyInfo.STACK_STATE_STATE_IDLE, delta.getStackState());
+ assertEquals(800, delta.getControllerTxDurationMillis());
+ assertEquals(700, delta.getControllerRxDurationMillis());
+ assertEquals(600, delta.getControllerScanDurationMillis());
+ assertEquals(500, delta.getControllerIdleDurationMillis());
+ }
+
+ @Test
public void testTargetedEnergyConsumerQuerying() {
final int numCpuClusters = 4;
final int numDisplays = 5;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index dd2b845..6f10370 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -5538,6 +5538,49 @@
}
@Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void removeAndAddAutomaticZenRule_ifChangingComponent_isAllowedAndDoesNotRestore() {
+ // Start with a rule.
+ mZenModeHelper.mConfig.automaticRules.clear();
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+ .setOwner(new ComponentName("first", "owner"))
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALL)
+ .build();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+ ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+
+ // User customizes it.
+ AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
+ "userUpdate", SYSTEM_UID);
+
+ // App deletes it. It's preserved for a possible restoration.
+ mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
+ CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
+ assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
+
+ // App adds it again, but this time with a different owner!
+ AutomaticZenRule readdingWithDifferentOwner = new AutomaticZenRule.Builder(rule)
+ .setOwner(new ComponentName("second", "owner"))
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+ .build();
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ readdingWithDifferentOwner, ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+
+ // Verify that the rule was NOT restored:
+ assertThat(newRuleId).isNotEqualTo(ruleId);
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
+ assertThat(finalRule.getOwner()).isEqualTo(new ComponentName("second", "owner"));
+
+ // Also, we discarded the "deleted rule" since we found it but decided not to use it.
+ assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
+ }
+
+ @Test
@EnableFlags(FLAG_MODES_API)
public void removeAutomaticZenRule_preservedForRestoringByPackageAndConditionId() {
mContext.getTestablePermissions().setPermission(Manifest.permission.MANAGE_NOTIFICATIONS,
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index ea825c7..1423811 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1629,10 +1629,10 @@
@Test
public void testCompleteResume_updateCompatDisplayInsets() {
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
- doReturn(true).when(activity).shouldCreateCompatDisplayInsets();
+ doReturn(true).when(activity).shouldCreateAppCompatDisplayInsets();
activity.setState(RESUMED, "test");
activity.completeResumeLocked();
- assertNotNull(activity.getCompatDisplayInsets());
+ assertNotNull(activity.getAppCompatDisplayInsets());
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index c788f3b..d7cef59 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -209,7 +209,7 @@
}
void setShouldCreateCompatDisplayInsets(boolean enabled) {
- doReturn(enabled).when(mActivityStack.top()).shouldCreateCompatDisplayInsets();
+ doReturn(enabled).when(mActivityStack.top()).shouldCreateAppCompatDisplayInsets();
}
void setTopActivityInSizeCompatMode(boolean inScm) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 1e1055b..5bb4378 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -957,12 +957,12 @@
.setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE)
.setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
.build();
- assertTrue(activity.shouldCreateCompatDisplayInsets());
+ assertTrue(activity.shouldCreateAppCompatDisplayInsets());
// The non-resizable activity should not be size compat because it is on a resizable task
// in multi-window mode.
mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
- assertFalse(activity.shouldCreateCompatDisplayInsets());
+ assertFalse(activity.shouldCreateAppCompatDisplayInsets());
// Activity should not be sandboxed.
assertMaxBoundsInheritDisplayAreaBounds();
@@ -971,7 +971,7 @@
mTask.mDisplayContent.getDefaultTaskDisplayArea()
.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
- assertFalse(activity.shouldCreateCompatDisplayInsets());
+ assertFalse(activity.shouldCreateAppCompatDisplayInsets());
// Activity should not be sandboxed.
assertMaxBoundsInheritDisplayAreaBounds();
}
@@ -986,7 +986,7 @@
// Create an activity on the same task.
final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */true,
RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- assertFalse(activity.shouldCreateCompatDisplayInsets());
+ assertFalse(activity.shouldCreateAppCompatDisplayInsets());
}
@Test
@@ -999,7 +999,7 @@
// Create an activity on the same task.
final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(activity.shouldCreateCompatDisplayInsets());
+ assertTrue(activity.shouldCreateAppCompatDisplayInsets());
}
@Test
@@ -1012,7 +1012,7 @@
// Create an activity on the same task.
final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
RESIZE_MODE_RESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- assertFalse(activity.shouldCreateCompatDisplayInsets());
+ assertFalse(activity.shouldCreateAppCompatDisplayInsets());
}
@Test
@@ -1026,7 +1026,7 @@
// Create an activity on the same task.
final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
- assertFalse(activity.shouldCreateCompatDisplayInsets());
+ assertFalse(activity.shouldCreateAppCompatDisplayInsets());
}
@Test
@@ -1040,7 +1040,7 @@
// Create an activity on the same task.
final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- assertFalse(activity.shouldCreateCompatDisplayInsets());
+ assertFalse(activity.shouldCreateAppCompatDisplayInsets());
}
@Test
@@ -1054,7 +1054,7 @@
// Create an activity on the same task.
final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */true,
RESIZE_MODE_RESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(activity.shouldCreateCompatDisplayInsets());
+ assertTrue(activity.shouldCreateAppCompatDisplayInsets());
}
@Test
@@ -1068,7 +1068,7 @@
// Create an activity on the same task.
final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */true,
RESIZE_MODE_RESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
- assertTrue(activity.shouldCreateCompatDisplayInsets());
+ assertTrue(activity.shouldCreateAppCompatDisplayInsets());
}
@Test
@@ -1090,7 +1090,7 @@
doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
.when(activity.mAppCompatController.getAppCompatAspectRatioOverrides())
.getUserMinAspectRatioOverrideCode();
- assertFalse(activity.shouldCreateCompatDisplayInsets());
+ assertFalse(activity.shouldCreateAppCompatDisplayInsets());
}
@Test
@@ -1110,7 +1110,7 @@
doReturn(true).when(
activity.mAppCompatController.getAppCompatAspectRatioOverrides())
.isSystemOverrideToFullscreenEnabled();
- assertFalse(activity.shouldCreateCompatDisplayInsets());
+ assertFalse(activity.shouldCreateAppCompatDisplayInsets());
}
@Test
@@ -3256,7 +3256,7 @@
// Activity max bounds are sandboxed since app may enter size compat mode.
assertActivityMaxBoundsSandboxed();
assertFalse(mActivity.inSizeCompatMode());
- assertTrue(mActivity.shouldCreateCompatDisplayInsets());
+ assertTrue(mActivity.shouldCreateAppCompatDisplayInsets());
// Resize display to half the width.
resizeDisplay(mActivity.getDisplayContent(), 500, 1000);
@@ -4102,8 +4102,8 @@
// To force config to update again but with the same landscape orientation.
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
- assertTrue(activity.shouldCreateCompatDisplayInsets());
- assertNotNull(activity.getCompatDisplayInsets());
+ assertTrue(activity.shouldCreateAppCompatDisplayInsets());
+ assertNotNull(activity.getAppCompatDisplayInsets());
// Activity is not letterboxed for fixed orientation because orientation is respected
// with insets, and should not be in size compat mode
assertFalse(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index 6fd5faf..b595383 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -34,6 +34,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
@@ -50,6 +51,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -839,4 +841,16 @@
0 /* launchFlags */, pinnedTask /* candidateTask */);
assertNull(actualRootTask);
}
+
+ @Test
+ public void testMovedRootTaskToFront() {
+ final TaskDisplayArea tda = mDefaultDisplay.getDefaultTaskDisplayArea();
+ final Task rootTask = createTask(tda, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+ true /* onTop */, true /* createActivity */, true /* twoLevelTask */);
+ final Task leafTask = rootTask.getTopLeafTask();
+
+ clearInvocations(tda);
+ tda.onTaskMoved(rootTask, true /* toTop */, false /* toBottom */);
+ verify(tda).onLeafTaskMoved(eq(leafTask), anyBoolean(), anyBoolean());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 0a592f2..55f74e9d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -867,8 +867,8 @@
// Without limiting to be inside the parent bounds, the out screen size should keep relative
// to the input bounds.
final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
- final ActivityRecord.CompatDisplayInsets compatInsets =
- new ActivityRecord.CompatDisplayInsets(
+ final AppCompatDisplayInsets compatInsets =
+ new AppCompatDisplayInsets(
display, activity, /* letterboxedContainerBounds */ null,
/* useOverrideInsets */ false);
final TaskFragment.ConfigOverrideHint overrideHint = new TaskFragment.ConfigOverrideHint();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
index 29f6360..fa28d11 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
@@ -322,9 +322,9 @@
a.checkTopActivityInSizeCompatMode(/* inScm */ true);
ta.launchTransparentActivityInTask();
- a.assertNotNullOnTopActivity(ActivityRecord::getCompatDisplayInsets);
+ a.assertNotNullOnTopActivity(ActivityRecord::getAppCompatDisplayInsets);
a.applyToTopActivity(ActivityRecord::clearSizeCompatMode);
- a.assertNullOnTopActivity(ActivityRecord::getCompatDisplayInsets);
+ a.assertNullOnTopActivity(ActivityRecord::getAppCompatDisplayInsets);
});
});
});
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java
new file mode 100644
index 0000000..9d56a92
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.internal.protolog.common.IProtoLogGroup;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test class for {@link ProtoLog}. */
+@SuppressWarnings("ConstantConditions")
+@Presubmit
+@RunWith(JUnit4.class)
+public class ProtoLogTest {
+
+ @Test
+ public void canRunProtoLogInitMultipleTimes() {
+ ProtoLog.init(TEST_GROUP_1);
+ ProtoLog.init(TEST_GROUP_1);
+ ProtoLog.init(TEST_GROUP_2);
+ ProtoLog.init(TEST_GROUP_1, TEST_GROUP_2);
+
+ final var instance = ProtoLog.getSingleInstance();
+ Truth.assertThat(instance.getRegisteredGroups())
+ .containsExactly(TEST_GROUP_1, TEST_GROUP_2);
+ }
+
+ private static final IProtoLogGroup TEST_GROUP_1 = new ProtoLogGroup("TEST_TAG_1", 1);
+ private static final IProtoLogGroup TEST_GROUP_2 = new ProtoLogGroup("TEST_TAG_2", 2);
+
+ private static class ProtoLogGroup implements IProtoLogGroup {
+ private final boolean mEnabled;
+ private volatile boolean mLogToProto;
+ private volatile boolean mLogToLogcat;
+ private final String mTag;
+ private final int mId;
+
+ ProtoLogGroup(String tag, int id) {
+ this(true, true, false, tag, id);
+ }
+
+ ProtoLogGroup(
+ boolean enabled, boolean logToProto, boolean logToLogcat, String tag, int id) {
+ this.mEnabled = enabled;
+ this.mLogToProto = logToProto;
+ this.mLogToLogcat = logToLogcat;
+ this.mTag = tag;
+ this.mId = id;
+ }
+
+ @Override
+ public String name() {
+ return mTag;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ @Override
+ public boolean isLogToProto() {
+ return mLogToProto;
+ }
+
+ @Override
+ public boolean isLogToLogcat() {
+ return mLogToLogcat;
+ }
+
+ @Override
+ public boolean isLogToAny() {
+ return mLogToLogcat || mLogToProto;
+ }
+
+ @Override
+ public String getTag() {
+ return mTag;
+ }
+
+ @Override
+ public void setLogToProto(boolean logToProto) {
+ this.mLogToProto = logToProto;
+ }
+
+ @Override
+ public void setLogToLogcat(boolean logToLogcat) {
+ this.mLogToLogcat = logToLogcat;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+}