Merge "[sb] status_bar_simple_fragment -> status_bar_root_modernization" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index eb49739..664b3dd 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -314,6 +314,7 @@
field public static final String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW";
field public static final String TRANSMIT_IR = "android.permission.TRANSMIT_IR";
field public static final String TURN_SCREEN_ON = "android.permission.TURN_SCREEN_ON";
+ field @FlaggedApi("android.app.enable_tv_implicit_enter_pip_restriction") public static final String TV_IMPLICIT_ENTER_PIP = "android.permission.TV_IMPLICIT_ENTER_PIP";
field public static final String UNINSTALL_SHORTCUT = "com.android.launcher.permission.UNINSTALL_SHORTCUT";
field public static final String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS";
field public static final String UPDATE_PACKAGES_WITHOUT_USER_ACTION = "android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION";
@@ -8839,7 +8840,7 @@
field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
- field public static final String PROPERTY_RETURN_VALUE = "returnValue";
+ field public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
field public static final int RESULT_APP_UNKNOWN_ERROR = 3000; // 0xbb8
field public static final int RESULT_CANCELLED = 2001; // 0x7d1
field public static final int RESULT_DENIED = 1000; // 0x3e8
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a9b181d..30fe244 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -706,6 +706,7 @@
field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images";
field public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video";
field public static final String OPSTR_READ_MEDIA_VISUAL_USER_SELECTED = "android:read_media_visual_user_selected";
+ field @FlaggedApi("android.permission.flags.platform_oxygen_saturation_enabled") public static final String OPSTR_READ_OXYGEN_SATURATION = "android:read_oxygen_saturation";
field @FlaggedApi("android.permission.flags.platform_skin_temperature_enabled") public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature";
field public static final String OPSTR_READ_WRITE_HEALTH_DATA = "android:read_write_health_data";
field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio";
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 7a1c759..3fccc17 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -30,6 +30,7 @@
import static java.lang.Character.MIN_VALUE;
+import android.Manifest;
import android.annotation.AnimRes;
import android.annotation.CallSuper;
import android.annotation.CallbackExecutor;
@@ -3193,6 +3194,16 @@
return ActivityTaskManager.getMaxNumPictureInPictureActions(this);
}
+ private boolean isImplicitEnterPipProhibited() {
+ PackageManager pm = getPackageManager();
+ if (android.app.Flags.enableTvImplicitEnterPipRestriction()) {
+ return pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+ && pm.checkPermission(Manifest.permission.TV_IMPLICIT_ENTER_PIP,
+ getPackageName()) == PackageManager.PERMISSION_DENIED;
+ }
+ return false;
+ }
+
/**
* @return Whether this device supports picture-in-picture.
*/
@@ -9192,6 +9203,8 @@
}
dispatchActivityPreResumed();
+ mCanEnterPictureInPicture = true;
+
mFragments.execPendingActions();
mLastNonConfigurationInstances = null;
@@ -9243,6 +9256,11 @@
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performPause:"
+ mComponent.getClassName());
}
+
+ if (isImplicitEnterPipProhibited()) {
+ mCanEnterPictureInPicture = false;
+ }
+
dispatchActivityPrePaused();
mDoReportFullyDrawn = false;
mFragments.dispatchPause();
@@ -9265,6 +9283,10 @@
final void performUserLeaving() {
onUserInteraction();
+
+ if (isImplicitEnterPipProhibited()) {
+ mCanEnterPictureInPicture = false;
+ }
onUserLeaveHint();
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 38c8583..fd70f4f 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1621,9 +1621,12 @@
*/
public static final int OP_RANGING = AppOpEnums.APP_OP_RANGING;
+ /** @hide Access to read oxygen saturation. */
+ public static final int OP_READ_OXYGEN_SATURATION = AppOpEnums.APP_OP_READ_OXYGEN_SATURATION;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 152;
+ public static final int _NUM_OP = 153;
/**
* All app ops represented as strings.
@@ -1779,6 +1782,7 @@
OPSTR_READ_HEART_RATE,
OPSTR_READ_SKIN_TEMPERATURE,
OPSTR_RANGING,
+ OPSTR_READ_OXYGEN_SATURATION,
})
public @interface AppOpString {}
@@ -2521,6 +2525,11 @@
@FlaggedApi(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
public static final String OPSTR_READ_HEART_RATE = "android:read_heart_rate";
+ /** @hide Access to read oxygen saturation. */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_PLATFORM_OXYGEN_SATURATION_ENABLED)
+ public static final String OPSTR_READ_OXYGEN_SATURATION = "android:read_oxygen_saturation";
+
/** @hide Access to read skin temperature. */
@SystemApi
@FlaggedApi(Flags.FLAG_PLATFORM_SKIN_TEMPERATURE_ENABLED)
@@ -2608,6 +2617,7 @@
// Health
Flags.replaceBodySensorPermissionEnabled() ? OP_READ_HEART_RATE : OP_NONE,
Flags.platformSkinTemperatureEnabled() ? OP_READ_SKIN_TEMPERATURE : OP_NONE,
+ Flags.platformOxygenSaturationEnabled() ? OP_READ_OXYGEN_SATURATION : OP_NONE,
};
/**
@@ -3129,6 +3139,11 @@
.setPermission(Flags.rangingPermissionEnabled()?
Manifest.permission.RANGING : null)
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_READ_OXYGEN_SATURATION, OPSTR_READ_OXYGEN_SATURATION,
+ "READ_OXYGEN_SATURATION").setPermission(
+ Flags.platformOxygenSaturationEnabled()
+ ? HealthPermissions.READ_OXYGEN_SATURATION : null)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
index 41bb622..1557815 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
@@ -111,8 +111,8 @@
* Returns the function parameters. The key is the parameter name, and the value is the
* parameter value.
*
- * <p>The bundle may have missing parameters. Developers are advised to implement defensive
- * handling measures.
+ * <p>The {@link GenericDocument} may have missing parameters. Developers are advised to
+ * implement defensive handling measures.
*
* @see AppFunctionManager on how to determine the expected parameters.
*/
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index cdf02e6..ced4b55 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -71,7 +71,7 @@
*
* <p>See {@link #getResultDocument} for more information on extracting the return value.
*/
- public static final String PROPERTY_RETURN_VALUE = "returnValue";
+ public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
/**
* The call was successful.
diff --git a/core/java/android/app/multitasking.aconfig b/core/java/android/app/multitasking.aconfig
index 9a64519..c8455c1 100644
--- a/core/java/android/app/multitasking.aconfig
+++ b/core/java/android/app/multitasking.aconfig
@@ -8,3 +8,11 @@
description: "Enables PiP UI state callback on entering"
bug: "303718131"
}
+
+flag {
+ name: "enable_tv_implicit_enter_pip_restriction"
+ is_exported: true
+ namespace: "tv_system_ui"
+ description: "Enables restrictions to PiP entry on TV for setAutoEnterEnabled and lifecycle methods"
+ bug: "283115999"
+}
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index ff0bb25..cc57dc0 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -398,6 +398,7 @@
* Retrieve the raw Intent contained in this Item.
*/
public Intent getIntent() {
+ Intent.maybeMarkAsMissingCreatorToken(mIntent);
return mIntent;
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 6fa5a9b..c054b79 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -87,6 +87,7 @@
import android.util.Log;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
import com.android.modules.expresslog.Counter;
@@ -108,6 +109,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.TimeZone;
+import java.util.function.Consumer;
/**
* An intent is an abstract description of an operation to be performed. It
@@ -892,6 +894,20 @@
public static void maybeMarkAsMissingCreatorToken(Object object) {
if (object instanceof Intent intent) {
maybeMarkAsMissingCreatorTokenInternal(intent);
+ } else if (object instanceof Parcelable[] parcelables) {
+ for (Parcelable p : parcelables) {
+ if (p instanceof Intent intent) {
+ maybeMarkAsMissingCreatorTokenInternal(intent);
+ }
+ }
+ } else if (object instanceof ArrayList parcelables) {
+ int N = parcelables.size();
+ for (int i = 0; i < N; i++) {
+ Object p = parcelables.get(i);
+ if (p instanceof Intent intent) {
+ maybeMarkAsMissingCreatorTokenInternal(intent);
+ }
+ }
}
}
@@ -12204,7 +12220,70 @@
// Stores a creator token for an intent embedded as an extra intent in a top level intent,
private IBinder mCreatorToken;
// Stores all extra keys whose values are intents for a top level intent.
- private ArraySet<String> mExtraIntentKeys;
+ private ArraySet<NestedIntentKey> mNestedIntentKeys;
+ }
+
+ /**
+ * @hide
+ */
+ public static class NestedIntentKey {
+ /** @hide */
+ @IntDef(flag = true, prefix = {"NESTED_INTENT_KEY_TYPE"}, value = {
+ NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL,
+ NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY,
+ NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST,
+ NESTED_INTENT_KEY_TYPE_CLIP_DATA,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface NestedIntentKeyType {
+ }
+
+ /**
+ * This flag indicates the key is for an extra parcel in mExtras.
+ */
+ private static final int NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL = 1 << 0;
+
+ /**
+ * This flag indicates the key is for an extra parcel array in mExtras and the index is the
+ * index of that array.
+ */
+ private static final int NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY = 1 << 1;
+
+ /**
+ * This flag indicates the key is for an extra parcel list in mExtras and the index is the
+ * index of that list.
+ */
+ private static final int NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST = 1 << 2;
+
+ /**
+ * This flag indicates the key is for an extra parcel in mClipData.mItems.
+ */
+ private static final int NESTED_INTENT_KEY_TYPE_CLIP_DATA = 1 << 3;
+
+ // type can be a short or even byte. But then probably cannot use @IntDef?? Also not sure
+ // if it is necessary.
+ private final @NestedIntentKeyType int mType;
+ private final String mKey;
+ private final int mIndex;
+
+ private NestedIntentKey(@NestedIntentKeyType int type, String key, int index) {
+ this.mType = type;
+ this.mKey = key;
+ this.mIndex = index;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ NestedIntentKey that = (NestedIntentKey) o;
+ return mType == that.mType && mIndex == that.mIndex && Objects.equals(mKey, that.mKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mType, mKey, mIndex);
+ }
}
private @Nullable CreatorTokenInfo mCreatorTokenInfo;
@@ -12227,8 +12306,9 @@
}
/** @hide */
- public Set<String> getExtraIntentKeys() {
- return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mExtraIntentKeys;
+ @VisibleForTesting
+ public Set<NestedIntentKey> getExtraIntentKeys() {
+ return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mNestedIntentKeys;
}
/** @hide */
@@ -12246,45 +12326,168 @@
* @hide
*/
public void collectExtraIntentKeys() {
- if (!preventIntentRedirect()) return;
+ if (preventIntentRedirect()) {
+ collectNestedIntentKeysRecur(new ArraySet<>());
+ }
+ }
- if (mExtras != null && !mExtras.isEmpty()) {
+ private void collectNestedIntentKeysRecur(Set<Intent> visited) {
+ if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) {
for (String key : mExtras.keySet()) {
- if (mExtras.get(key) instanceof Intent) {
- if (mCreatorTokenInfo == null) {
- mCreatorTokenInfo = new CreatorTokenInfo();
- }
- if (mCreatorTokenInfo.mExtraIntentKeys == null) {
- mCreatorTokenInfo.mExtraIntentKeys = new ArraySet<>();
- }
- mCreatorTokenInfo.mExtraIntentKeys.add(key);
+ Object value = mExtras.get(key);
+
+ if (value instanceof Intent intent && !visited.contains(intent)) {
+ handleNestedIntent(intent, visited, new NestedIntentKey(
+ NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL, key, 0));
+ } else if (value instanceof Parcelable[] parcelables) {
+ handleParcelableArray(parcelables, key, visited);
+ } else if (value instanceof ArrayList<?> parcelables) {
+ handleParcelableList(parcelables, key, visited);
+ }
+ }
+ }
+
+ if (mClipData != null) {
+ for (int i = 0; i < mClipData.getItemCount(); i++) {
+ Intent intent = mClipData.getItemAt(i).mIntent;
+ if (intent != null && !visited.contains(intent)) {
+ handleNestedIntent(intent, visited, new NestedIntentKey(
+ NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA, null, i));
}
}
}
}
+ private void handleNestedIntent(Intent intent, Set<Intent> visited, NestedIntentKey key) {
+ visited.add(intent);
+ if (mCreatorTokenInfo == null) {
+ mCreatorTokenInfo = new CreatorTokenInfo();
+ }
+ if (mCreatorTokenInfo.mNestedIntentKeys == null) {
+ mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>();
+ }
+ mCreatorTokenInfo.mNestedIntentKeys.add(key);
+ intent.collectNestedIntentKeysRecur(visited);
+ }
+
+ private void handleParcelableArray(Parcelable[] parcelables, String key, Set<Intent> visited) {
+ for (int i = 0; i < parcelables.length; i++) {
+ if (parcelables[i] instanceof Intent intent && !visited.contains(intent)) {
+ handleNestedIntent(intent, visited, new NestedIntentKey(
+ NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY, key, i));
+ }
+ }
+ }
+
+ private void handleParcelableList(ArrayList<?> parcelables, String key, Set<Intent> visited) {
+ for (int i = 0; i < parcelables.size(); i++) {
+ if (parcelables.get(i) instanceof Intent intent && !visited.contains(intent)) {
+ handleNestedIntent(intent, visited, new NestedIntentKey(
+ NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST, key, i));
+ }
+ }
+ }
+
+ private static final Consumer<Intent> CHECK_CREATOR_TOKEN_ACTION = intent -> {
+ intent.mLocalFlags |= LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT;
+ if (intent.mExtras != null) {
+ intent.mExtras.enableTokenVerification();
+ }
+ };
+
/** @hide */
public void checkCreatorToken() {
- if (mExtras == null) return;
- if (mCreatorTokenInfo != null && mCreatorTokenInfo.mExtraIntentKeys != null) {
- for (String key : mCreatorTokenInfo.mExtraIntentKeys) {
- try {
- Intent extraIntent = mExtras.getParcelable(key, Intent.class);
- if (extraIntent == null) {
- Log.w(TAG, "The key {" + key
- + "} does not correspond to an intent in the bundle.");
- continue;
- }
- extraIntent.mLocalFlags |= LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT;
- } catch (Exception e) {
- Log.e(TAG, "Failed to validate creator token. key: " + key + ".", e);
+ forEachNestedCreatorToken(CHECK_CREATOR_TOKEN_ACTION);
+
+ if (mExtras != null) {
+ // mark the bundle as intent extras after calls to getParcelable.
+ // otherwise, the logic to mark missing token would run before
+ // mark trusted creator token present.
+ mExtras.enableTokenVerification();
+ }
+ }
+
+ /** @hide */
+ public void forEachNestedCreatorToken(Consumer<? super Intent> action) {
+ if (mExtras == null && mClipData == null) return;
+
+ if (mCreatorTokenInfo != null && mCreatorTokenInfo.mNestedIntentKeys != null) {
+ int N = mCreatorTokenInfo.mNestedIntentKeys.size();
+ for (int i = 0; i < N; i++) {
+ NestedIntentKey key = mCreatorTokenInfo.mNestedIntentKeys.valueAt(i);
+ Intent extraIntent = extractIntentFromKey(key);
+
+ if (extraIntent != null) {
+ action.accept(extraIntent);
+ extraIntent.forEachNestedCreatorToken(action);
+ } else {
+ Log.w(TAG, getLogMessageForKey(key));
}
}
}
- // mark the bundle as intent extras after calls to getParcelable.
- // otherwise, the logic to mark missing token would run before
- // mark trusted creator token present.
- mExtras.setIsIntentExtra();
+ }
+
+ private Intent extractIntentFromKey(NestedIntentKey key) {
+ switch (key.mType) {
+ case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL:
+ return mExtras == null ? null : mExtras.getParcelable(key.mKey, Intent.class);
+ case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY:
+ if (mExtras == null) return null;
+ Intent[] extraIntents = mExtras.getParcelableArray(key.mKey, Intent.class);
+ if (extraIntents != null && key.mIndex < extraIntents.length) {
+ return extraIntents[key.mIndex];
+ }
+ break;
+ case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST:
+ if (mExtras == null) return null;
+ ArrayList<Intent> extraIntentsList = mExtras.getParcelableArrayList(key.mKey,
+ Intent.class);
+ if (extraIntentsList != null && key.mIndex < extraIntentsList.size()) {
+ return extraIntentsList.get(key.mIndex);
+ }
+ break;
+ case NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA:
+ if (mClipData == null) return null;
+ if (key.mIndex < mClipData.getItemCount()) {
+ ClipData.Item item = mClipData.getItemAt(key.mIndex);
+ if (item != null) {
+ return item.mIntent;
+ }
+ }
+ break;
+ }
+ return null;
+ }
+
+ private String getLogMessageForKey(NestedIntentKey key) {
+ switch (key.mType) {
+ case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL:
+ return "The key {" + key + "} does not correspond to an intent in the bundle.";
+ case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY:
+ if (mExtras.getParcelableArray(key.mKey, Intent.class) == null) {
+ return "The key {" + key
+ + "} does not correspond to a Parcelable[] in the bundle.";
+ } else {
+ return "Parcelable[" + key.mIndex + "] for key {" + key + "} is not an intent.";
+ }
+ case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST:
+ if (mExtras.getParcelableArrayList(key.mKey, Intent.class) == null) {
+ return "The key {" + key
+ + "} does not correspond to an ArrayList<Parcelable> in the bundle.";
+ } else {
+ return "List.get(" + key.mIndex + ") for key {" + key + "} is not an intent.";
+ }
+ case NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA:
+ if (key.mIndex >= mClipData.getItemCount()) {
+ return "Index out of range for clipData items. index: " + key.mIndex
+ + ". item counts: " + mClipData.getItemCount();
+ } else {
+ return "clipData items at index [" + key.mIndex
+ + "] is null or does not contain an intent.";
+ }
+ default:
+ return "Unknown key type: " + key.mType;
+ }
}
/**
@@ -12357,7 +12560,19 @@
} else {
out.writeInt(1);
out.writeStrongBinder(mCreatorTokenInfo.mCreatorToken);
- out.writeArraySet(mCreatorTokenInfo.mExtraIntentKeys);
+
+ if (mCreatorTokenInfo.mNestedIntentKeys != null) {
+ final int N = mCreatorTokenInfo.mNestedIntentKeys.size();
+ out.writeInt(N);
+ for (int i = 0; i < N; i++) {
+ NestedIntentKey key = mCreatorTokenInfo.mNestedIntentKeys.valueAt(i);
+ out.writeInt(key.mType);
+ out.writeString8(key.mKey);
+ out.writeInt(key.mIndex);
+ }
+ } else {
+ out.writeInt(0);
+ }
}
}
}
@@ -12422,7 +12637,18 @@
if (in.readInt() != 0) {
mCreatorTokenInfo = new CreatorTokenInfo();
mCreatorTokenInfo.mCreatorToken = in.readStrongBinder();
- mCreatorTokenInfo.mExtraIntentKeys = (ArraySet<String>) in.readArraySet(null);
+
+ N = in.readInt();
+ if (N > 0) {
+ mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>(N);
+ for (int i = 0; i < N; i++) {
+ int type = in.readInt();
+ String key = in.readString8();
+ int index = in.readInt();
+ mCreatorTokenInfo.mNestedIntentKeys.append(
+ new NestedIntentKey(type, key, index));
+ }
+ }
}
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index e22c263..1cc0856 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -437,7 +437,7 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public synchronized void writeToParcel(Parcel dest, int flags) {
nativeWriteToParcel(dest, mMetadataPtr);
}
@@ -479,7 +479,7 @@
return getBase(key);
}
- public void readFromParcel(Parcel in) {
+ public synchronized void readFromParcel(Parcel in) {
nativeReadFromParcel(in, mMetadataPtr);
updateNativeAllocation();
}
@@ -592,28 +592,33 @@
}
private <T> T getBase(Key<T> key) {
- int tag;
- if (key.hasTag()) {
- tag = key.getTag();
- } else {
- tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.getName());
- key.cacheTag(tag);
- }
- byte[] values = readValues(tag);
- if (values == null) {
- // If the key returns null, use the fallback key if exists.
- // This is to support old key names for the newly published keys.
- if (key.mFallbackName == null) {
- return null;
+ int tag, nativeType;
+ byte[] values = null;
+ synchronized (this) {
+ if (key.hasTag()) {
+ tag = key.getTag();
+ } else {
+ tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.getName());
+ key.cacheTag(tag);
}
- tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.mFallbackName);
values = readValues(tag);
if (values == null) {
- return null;
+ // If the key returns null, use the fallback key if exists.
+ // This is to support old key names for the newly published keys.
+ if (key.mFallbackName == null) {
+ return null;
+ }
+ tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.mFallbackName);
+ values = readValues(tag);
+ if (values == null) {
+ return null;
+ }
}
- }
- int nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag);
+ nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag);
+ }
+ // This block of code doesn't need to be synchronized since we aren't writing or reading
+ // from the metadata buffer for this instance of CameraMetadataNative.
Marshaler<T> marshaler = getMarshalerForKey(key, nativeType);
ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder());
return marshaler.unmarshal(buffer);
@@ -1945,8 +1950,12 @@
setBase(key.getNativeKey(), value);
}
- private <T> void setBase(Key<T> key, T value) {
- int tag;
+ // The whole method needs to be synchronized since we're making
+ // multiple calls to the native layer. From one call to the other (within setBase)
+ // we expect the metadata's properties such as vendor id etc to
+ // stay the same and as a result the whole method should be synchronized for safety.
+ private synchronized <T> void setBase(Key<T> key, T value) {
+ int tag, nativeType;
if (key.hasTag()) {
tag = key.getTag();
} else {
@@ -1959,7 +1968,7 @@
return;
} // else update the entry to a new value
- int nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag);
+ nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag);
Marshaler<T> marshaler = getMarshalerForKey(key, nativeType);
int size = marshaler.calculateMarshalSize(value);
@@ -2162,7 +2171,7 @@
return true;
}
- private void updateNativeAllocation() {
+ private synchronized void updateNativeAllocation() {
long currentBufferSize = nativeGetBufferSize(mMetadataPtr);
if (currentBufferSize != mBufferSize) {
@@ -2245,6 +2254,11 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private long mMetadataPtr; // native std::shared_ptr<CameraMetadata>*
+ // FastNative doesn't work with synchronized methods and we can do synchronization
+ // wherever needed in the java layer (caller). At some places in java such as
+ // setBase() / getBase(), we do need to synchronize the whole method, so leaving
+ // synchronized out for these native methods.
+
@FastNative
private static native long nativeAllocate();
@FastNative
@@ -2254,28 +2268,41 @@
@FastNative
private static native void nativeUpdate(long dst, long src);
- private static synchronized native void nativeWriteToParcel(Parcel dest, long ptr);
- private static synchronized native void nativeReadFromParcel(Parcel source, long ptr);
- private static synchronized native void nativeSwap(long ptr, long otherPtr)
+ @FastNative
+ private static native void nativeWriteToParcel(Parcel dest, long ptr);
+ @FastNative
+ private static native void nativeReadFromParcel(Parcel source, long ptr);
+ @FastNative
+ private static native void nativeSwap(long ptr, long otherPtr)
throws NullPointerException;
@FastNative
private static native void nativeSetVendorId(long ptr, long vendorId);
- private static synchronized native void nativeClose(long ptr);
- private static synchronized native boolean nativeIsEmpty(long ptr);
- private static synchronized native int nativeGetEntryCount(long ptr);
- private static synchronized native long nativeGetBufferSize(long ptr);
+ @FastNative
+ private static native void nativeClose(long ptr);
+ @FastNative
+ private static native boolean nativeIsEmpty(long ptr);
+ @FastNative
+ private static native int nativeGetEntryCount(long ptr);
+ @FastNative
+ private static native long nativeGetBufferSize(long ptr);
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- private static synchronized native byte[] nativeReadValues(int tag, long ptr);
- private static synchronized native void nativeWriteValues(int tag, byte[] src, long ptr);
- private static synchronized native void nativeDump(long ptr) throws IOException; // dump to LOGD
+ @FastNative
+ private static native byte[] nativeReadValues(int tag, long ptr);
+ @FastNative
+ private static native void nativeWriteValues(int tag, byte[] src, long ptr);
+ @FastNative
+ private static native void nativeDump(long ptr) throws IOException; // dump to LOGD
- private static synchronized native ArrayList nativeGetAllVendorKeys(long ptr, Class keyClass);
+ @FastNative
+ private static native ArrayList nativeGetAllVendorKeys(long ptr, Class keyClass);
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- private static synchronized native int nativeGetTagFromKeyLocal(long ptr, String keyName)
+ @FastNative
+ private static native int nativeGetTagFromKeyLocal(long ptr, String keyName)
throws IllegalArgumentException;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- private static synchronized native int nativeGetTypeFromTagLocal(long ptr, int tag)
+ @FastNative
+ private static native int nativeGetTypeFromTagLocal(long ptr, int tag)
throws IllegalArgumentException;
@FastNative
private static native int nativeGetTagFromKey(String keyName, long vendorId)
@@ -2293,7 +2320,7 @@
* @throws NullPointerException if other was null
* @hide
*/
- public void swap(CameraMetadataNative other) {
+ public synchronized void swap(CameraMetadataNative other) {
nativeSwap(mMetadataPtr, other.mMetadataPtr);
mCameraId = other.mCameraId;
mHasMandatoryConcurrentStreams = other.mHasMandatoryConcurrentStreams;
@@ -2308,14 +2335,14 @@
*
* @hide
*/
- public void setVendorId(long vendorId) {
+ public synchronized void setVendorId(long vendorId) {
nativeSetVendorId(mMetadataPtr, vendorId);
}
/**
* @hide
*/
- public int getEntryCount() {
+ public synchronized int getEntryCount() {
return nativeGetEntryCount(mMetadataPtr);
}
@@ -2324,7 +2351,7 @@
*
* @hide
*/
- public boolean isEmpty() {
+ public synchronized boolean isEmpty() {
return nativeIsEmpty(mMetadataPtr);
}
@@ -2343,7 +2370,7 @@
*
* @hide
*/
- public <K> ArrayList<K> getAllVendorKeys(Class<K> keyClass) {
+ public synchronized <K> ArrayList<K> getAllVendorKeys(Class<K> keyClass) {
if (keyClass == null) {
throw new NullPointerException();
}
@@ -2398,7 +2425,7 @@
*
* @hide
*/
- public void writeValues(int tag, byte[] src) {
+ public synchronized void writeValues(int tag, byte[] src) {
nativeWriteValues(tag, src, mMetadataPtr);
}
@@ -2413,7 +2440,7 @@
* @return {@code null} if there were 0 entries for this tag, a byte[] otherwise.
* @hide
*/
- public byte[] readValues(int tag) {
+ public synchronized byte[] readValues(int tag) {
// TODO: Optimization. Native code returns a ByteBuffer instead.
return nativeReadValues(tag, mMetadataPtr);
}
@@ -2426,7 +2453,7 @@
*
* @hide
*/
- public void dumpToLog() {
+ public synchronized void dumpToLog() {
try {
nativeDump(mMetadataPtr);
} catch (IOException e) {
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index c18fb0c..99e7d166 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -281,7 +281,7 @@
}
/** {@hide} */
- public void setIsIntentExtra() {
+ public void enableTokenVerification() {
mFlags |= FLAG_VERIFY_TOKENS_PRESENT;
}
diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig
index 72f2de8..dfc11dc 100644
--- a/core/java/android/service/dreams/flags.aconfig
+++ b/core/java/android/service/dreams/flags.aconfig
@@ -67,3 +67,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "cleanup_dream_settings_on_uninstall"
+ namespace: "systemui"
+ description: "Cleans up dream settings if dream package is uninstalled."
+ bug: "338210427"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 618843c..fa06831 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -23882,12 +23882,12 @@
} else {
draw(canvas);
}
- }
- // For VRR to vote the preferred frame rate
- if (sToolkitSetFrameRateReadOnlyFlagValue
- && sToolkitFrameRateViewEnablingReadOnlyFlagValue) {
- votePreferredFrameRate();
+ // For VRR to vote the preferred frame rate
+ if (sToolkitSetFrameRateReadOnlyFlagValue
+ && sToolkitFrameRateViewEnablingReadOnlyFlagValue) {
+ votePreferredFrameRate();
+ }
}
} finally {
renderNode.endRecording();
diff --git a/core/res/Android.bp b/core/res/Android.bp
index c66d2b5..73776f0 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -158,6 +158,7 @@
flags_packages: [
"android.app.appfunctions.flags-aconfig",
"android.app.contextualsearch.flags-aconfig",
+ "android.app.flags-aconfig",
"android.appwidget.flags-aconfig",
"android.content.pm.flags-aconfig",
"android.provider.flags-aconfig",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 44bda1c..0e4eb94 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4463,6 +4463,18 @@
android:description="@string/permdesc_hideOverlayWindows"
android:protectionLevel="normal" />
+ <!-- Allows an app to enter Picture-in-Picture mode when the user is not explicitly requesting
+ it. This includes using {@link PictureInPictureParams.Builder#setAutoEnterEnabled} as well
+ as lifecycle methods such as {@link Activity#onUserLeaveHint} and {@link Activity#onPause}
+ to enter PiP when the user leaves the app.
+ This permission should only be used for certain PiP
+ <a href="{@docRoot}training/tv/get-started/multitasking#usage-types">usage types</a>.
+ @FlaggedApi(android.app.Flags.FLAG_ENABLE_TV_IMPLICIT_ENTER_PIP_RESTRICTION)
+ -->
+ <permission android:name="android.permission.TV_IMPLICIT_ENTER_PIP"
+ android:protectionLevel="normal"
+ android:featureFlag="android.app.enable_tv_implicit_enter_pip_restriction" />
+
<!-- ================================== -->
<!-- Permissions affecting the system wallpaper -->
<!-- ================================== -->
diff --git a/core/tests/coretests/src/android/content/IntentTest.java b/core/tests/coretests/src/android/content/IntentTest.java
index d169ce3..7bc4abd 100644
--- a/core/tests/coretests/src/android/content/IntentTest.java
+++ b/core/tests/coretests/src/android/content/IntentTest.java
@@ -37,6 +37,10 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
/**
* Build/Install/Run:
* atest FrameworksCoreTests:IntentTest
@@ -57,7 +61,12 @@
public void testReadFromParcelWithExtraIntentKeys() {
Intent intent = new Intent("TEST_ACTION");
intent.putExtra(TEST_EXTRA_NAME, new Intent(TEST_ACTION));
+ // Not an intent, don't count.
intent.putExtra(TEST_EXTRA_NAME + "2", 1);
+ ArrayList<Intent> intents = new ArrayList<>();
+ intents.add(new Intent(TEST_ACTION));
+ intent.putParcelableArrayListExtra(TEST_EXTRA_NAME + "3", intents);
+ intent.setClipData(ClipData.newIntent("label", new Intent(TEST_ACTION)));
intent.collectExtraIntentKeys();
final Parcel parcel = Parcel.obtain();
@@ -68,7 +77,7 @@
assertEquals(intent.getAction(), target.getAction());
assertEquals(intent.getExtraIntentKeys(), target.getExtraIntentKeys());
- assertThat(intent.getExtraIntentKeys()).hasSize(1);
+ assertThat(intent.getExtraIntentKeys()).hasSize(3);
}
@Test
@@ -87,13 +96,37 @@
@RequiresFlagsEnabled(Flags.FLAG_PREVENT_INTENT_REDIRECT)
public void testCollectExtraIntentKeys() {
Intent intent = new Intent(TEST_ACTION);
- Intent extraIntent = new Intent(TEST_ACTION, TEST_URI);
- intent.putExtra(TEST_EXTRA_NAME, extraIntent);
+
+ Intent[] intents = new Intent[10];
+ for (int i = 0; i < intents.length; i++) {
+ intents[i] = new Intent("action" + i);
+ }
+ Intent[] intents2 = new Intent[2]; // intents[6-7]
+ System.arraycopy(intents, 6, intents2, 0, intents2.length);
+ ArrayList<Intent> intents3 = new ArrayList<>(2);
+ intents3.addAll(Arrays.asList(intents).subList(8, 10)); // intents[8-9]
+ intent.putExtra("key1", intents[0]);
+ intent.putExtra("array-key", intents2);
+ intent.setClipData(ClipData.newIntent("label2", intents[1]));
+ intent.putExtra("intkey", 1);
+ intents[0].putExtra("key3", intents[2]);
+ intents[0].setClipData(ClipData.newIntent("label4", intents[3]));
+ intents[0].putParcelableArrayListExtra("array-list-key", intents3);
+ intents[1].putExtra("key3", intents[4]);
+ intents[1].setClipData(ClipData.newIntent("label4", intents[5]));
+ intents[5].putExtra("intkey", 2);
intent.collectExtraIntentKeys();
- assertThat(intent.getExtraIntentKeys()).hasSize(1);
- assertThat(intent.getExtraIntentKeys()).contains(TEST_EXTRA_NAME);
+ // collect all actions of nested intents.
+ final List<String> actions = new ArrayList<>();
+ intent.forEachNestedCreatorToken(intent1 -> {
+ actions.add(intent1.getAction());
+ });
+ assertThat(actions).hasSize(10);
+ for (int i = 0; i < intents.length; i++) {
+ assertThat(actions).contains("action" + i);
+ }
}
}
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 4c47de0..d55a71e 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -1761,7 +1761,7 @@
if (Flags.displayBt2020Colorspace()) {
sNamedColorSpaceMap.put(Named.DISPLAY_BT2020.ordinal(), new ColorSpace.Rgb(
- "BT 2020",
+ "Display BT. 2020",
BT2020_PRIMARIES,
ILLUMINANT_D65,
null,
diff --git a/libs/appfunctions/Android.bp b/libs/appfunctions/Android.bp
index c6cee07..5ab5a7a 100644
--- a/libs/appfunctions/Android.bp
+++ b/libs/appfunctions/Android.bp
@@ -18,10 +18,10 @@
}
java_sdk_library {
- name: "com.google.android.appfunctions.sidecar",
+ name: "com.android.extensions.appfunctions",
owner: "google",
srcs: ["java/**/*.java"],
- api_packages: ["com.google.android.appfunctions.sidecar"],
+ api_packages: ["com.android.extensions.appfunctions"],
dex_preopt: {
enabled: false,
},
@@ -31,9 +31,9 @@
}
prebuilt_etc {
- name: "appfunctions.sidecar.xml",
+ name: "appfunctions.extension.xml",
system_ext_specific: true,
sub_dir: "permissions",
- src: "appfunctions.sidecar.xml",
+ src: "appfunctions.extension.xml",
filename_from_src: true,
}
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index faf84a8..0eda101 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -1,9 +1,9 @@
// Signature format: 2.0
-package com.google.android.appfunctions.sidecar {
+package com.android.extensions.appfunctions {
public final class AppFunctionManager {
ctor public AppFunctionManager(android.content.Context);
- method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.android.extensions.appfunctions.ExecuteAppFunctionResponse>);
method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
@@ -15,7 +15,7 @@
public abstract class AppFunctionService extends android.app.Service {
ctor public AppFunctionService();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
- method @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ method @MainThread public abstract void onExecuteFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.android.extensions.appfunctions.ExecuteAppFunctionResponse>);
field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE";
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
@@ -29,9 +29,9 @@
public static final class ExecuteAppFunctionRequest.Builder {
ctor public ExecuteAppFunctionRequest.Builder(@NonNull String, @NonNull String);
- method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest build();
- method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle);
- method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument);
+ method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest build();
+ method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle);
+ method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument);
}
public final class ExecuteAppFunctionResponse {
@@ -41,13 +41,13 @@
method public int getResultCode();
method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
method public boolean isSuccess();
- method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle);
- method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
+ method @NonNull public static com.android.extensions.appfunctions.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle);
+ method @NonNull public static com.android.extensions.appfunctions.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
field public static final int ERROR_CATEGORY_APP = 3; // 0x3
field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
- field public static final String PROPERTY_RETURN_VALUE = "returnValue";
+ field public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
field public static final int RESULT_APP_UNKNOWN_ERROR = 3000; // 0xbb8
field public static final int RESULT_CANCELLED = 2001; // 0x7d1
field public static final int RESULT_DENIED = 1000; // 0x3e8
diff --git a/libs/appfunctions/appfunctions.sidecar.xml b/libs/appfunctions/appfunctions.extension.xml
similarity index 83%
rename from libs/appfunctions/appfunctions.sidecar.xml
rename to libs/appfunctions/appfunctions.extension.xml
index bef8b6e..dd09cc3 100644
--- a/libs/appfunctions/appfunctions.sidecar.xml
+++ b/libs/appfunctions/appfunctions.extension.xml
@@ -16,6 +16,6 @@
-->
<permissions>
<library
- name="com.google.android.appfunctions.sidecar"
- file="/system_ext/framework/com.google.android.appfunctions.sidecar.jar"/>
+ name="com.android.extensions.appfunctions"
+ file="/system_ext/framework/com.android.extensions.appfunctions.jar"/>
</permissions>
\ No newline at end of file
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
similarity index 98%
rename from libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
rename to libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
index 2075104..d64593d 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.android.appfunctions.sidecar;
+package com.android.extensions.appfunctions;
import android.Manifest;
import android.annotation.CallbackExecutor;
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
similarity index 98%
rename from libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
rename to libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
index 0dc87e4..1a4d9da 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.android.appfunctions.sidecar;
+package com.android.extensions.appfunctions;
import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE;
@@ -26,7 +26,6 @@
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.IBinder;
-import android.util.Log;
import java.util.function.Consumer;
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java
similarity index 96%
rename from libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java
rename to libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java
index 593c521..baddc24 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.android.appfunctions.sidecar;
+package com.android.extensions.appfunctions;
import android.annotation.NonNull;
import android.app.appsearch.GenericDocument;
@@ -91,8 +91,8 @@
* Returns the function parameters. The key is the parameter name, and the value is the
* parameter value.
*
- * <p>The bundle may have missing parameters. Developers are advised to implement defensive
- * handling measures.
+ * <p>The {@link GenericDocument} may have missing parameters. Developers are advised to
+ * implement defensive handling measures.
*
* <p>Similar to {@link #getFunctionIdentifier()} the parameters required by a function can be
* obtained by querying AppSearch for the corresponding {@code AppFunctionStaticMetadata}. This
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java
similarity index 98%
rename from libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
rename to libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java
index 4e88fb0..7c5ddcd 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.android.appfunctions.sidecar;
+package com.android.extensions.appfunctions;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -48,7 +48,7 @@
*
* <p>See {@link #getResultDocument} for more information on extracting the return value.
*/
- public static final String PROPERTY_RETURN_VALUE = "returnValue";
+ public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
/**
* The call was successful.
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java
similarity index 69%
rename from libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java
rename to libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java
index b1b05f7..56f2725f 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.android.appfunctions.sidecar;
+package com.android.extensions.appfunctions;
import android.annotation.NonNull;
@@ -28,26 +28,24 @@
private SidecarConverter() {}
/**
- * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest}
- * into platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest}
+ * Converts sidecar's {@link ExecuteAppFunctionRequest} into platform's {@link
+ * android.app.appfunctions.ExecuteAppFunctionRequest}
*
* @hide
*/
@NonNull
public static android.app.appfunctions.ExecuteAppFunctionRequest
getPlatformExecuteAppFunctionRequest(@NonNull ExecuteAppFunctionRequest request) {
- return new
- android.app.appfunctions.ExecuteAppFunctionRequest.Builder(
- request.getTargetPackageName(),
- request.getFunctionIdentifier())
+ return new android.app.appfunctions.ExecuteAppFunctionRequest.Builder(
+ request.getTargetPackageName(), request.getFunctionIdentifier())
.setExtras(request.getExtras())
.setParameters(request.getParameters())
.build();
}
/**
- * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse}
- * into platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse}
+ * Converts sidecar's {@link ExecuteAppFunctionResponse} into platform's {@link
+ * android.app.appfunctions.ExecuteAppFunctionResponse}
*
* @hide
*/
@@ -59,15 +57,13 @@
response.getResultDocument(), response.getExtras());
} else {
return android.app.appfunctions.ExecuteAppFunctionResponse.newFailure(
- response.getResultCode(),
- response.getErrorMessage(),
- response.getExtras());
+ response.getResultCode(), response.getErrorMessage(), response.getExtras());
}
}
/**
- * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest}
- * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest}
+ * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} into sidecar's
+ * {@link ExecuteAppFunctionRequest}
*
* @hide
*/
@@ -75,16 +71,15 @@
public static ExecuteAppFunctionRequest getSidecarExecuteAppFunctionRequest(
@NonNull android.app.appfunctions.ExecuteAppFunctionRequest request) {
return new ExecuteAppFunctionRequest.Builder(
- request.getTargetPackageName(),
- request.getFunctionIdentifier())
+ request.getTargetPackageName(), request.getFunctionIdentifier())
.setExtras(request.getExtras())
.setParameters(request.getParameters())
.build();
}
/**
- * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse}
- * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse}
+ * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} into
+ * sidecar's {@link ExecuteAppFunctionResponse}
*
* @hide
*/
@@ -96,9 +91,7 @@
response.getResultDocument(), response.getExtras());
} else {
return ExecuteAppFunctionResponse.newFailure(
- response.getResultCode(),
- response.getErrorMessage(),
- response.getExtras());
+ response.getResultCode(), response.getErrorMessage(), response.getExtras());
}
}
}
diff --git a/libs/appfunctions/tests/Android.bp b/libs/appfunctions/tests/Android.bp
index 6f5eff3..db79675 100644
--- a/libs/appfunctions/tests/Android.bp
+++ b/libs/appfunctions/tests/Android.bp
@@ -25,7 +25,7 @@
"androidx.test.rules",
"androidx.test.ext.junit",
"androidx.core_core-ktx",
- "com.google.android.appfunctions.sidecar.impl",
+ "com.android.extensions.appfunctions.impl",
"junit",
"kotlin-test",
"mockito-target-extended-minus-junit4",
diff --git a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt
similarity index 93%
rename from libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt
rename to libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt
index 264f842..6118e6c 100644
--- a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt
+++ b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.google.android.appfunctions.sidecar.tests
+package com.android.extensions.appfunctions.tests
import android.app.appfunctions.ExecuteAppFunctionRequest
import android.app.appfunctions.ExecuteAppFunctionResponse
import android.app.appsearch.GenericDocument
import android.os.Bundle
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.google.android.appfunctions.sidecar.SidecarConverter
+import com.android.extensions.appfunctions.SidecarConverter
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -60,7 +60,7 @@
.setPropertyLong("testLong", 23)
.build()
val sidecarRequest =
- com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder(
+ com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder(
"targetPkg",
"targetFunctionId"
)
@@ -129,8 +129,11 @@
GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "")
.setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true)
.build()
- val sidecarResponse = com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse
- .newSuccess(resultGd, null)
+ val sidecarResponse =
+ com.android.extensions.appfunctions.ExecuteAppFunctionResponse.newSuccess(
+ resultGd,
+ null
+ )
val platformResponse = SidecarConverter.getPlatformExecuteAppFunctionResponse(
sidecarResponse
@@ -151,7 +154,7 @@
fun getPlatformExecuteAppFunctionResponse_errorResponse_sameContents() {
val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build()
val sidecarResponse =
- com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse.newFailure(
+ com.android.extensions.appfunctions.ExecuteAppFunctionResponse.newFailure(
ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
null,
null
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index 6b371d7..9b47ead 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -19,6 +19,7 @@
import android.app.admin.devicePolicyManager
import android.content.pm.UserInfo
+import android.internal.statusbar.fakeStatusBarService
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
@@ -61,6 +62,7 @@
private val globalSettings = kosmos.fakeGlobalSettings
private val broadcastDispatcher = kosmos.broadcastDispatcher
private val devicePolicyManager = kosmos.devicePolicyManager
+ private val statusBarService = kosmos.fakeStatusBarService
@Mock private lateinit var manager: UserManager
@@ -323,6 +325,8 @@
tracker = tracker,
broadcastDispatcher = broadcastDispatcher,
devicePolicyManager = devicePolicyManager,
+ resources = context.resources,
+ statusBarService = statusBarService,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt
index 26439df..f70b426 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt
@@ -49,35 +49,78 @@
@Before
fun setUp() {
userRepository.setUserInfos(USER_INFOS)
- runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[1]) }
+ runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[2]) }
+ userRepository.setLogoutToSystemUserEnabled(false)
+ userRepository.setSecondaryUserLogoutEnabled(false)
}
@Test
- fun logOut_doesNothing_whenAdminDisabledSecondaryLogout() {
+ fun logOut_doesNothing_whenBothLogoutOptionsAreDisabled() {
testScope.runTest {
val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled)
- val lastLogoutCount = userRepository.logOutSecondaryUserCallCount
- userRepository.setSecondaryUserLogoutEnabled(false)
+ val secondaryUserLogoutCount = userRepository.logOutSecondaryUserCallCount
+ val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount
assertThat(isLogoutEnabled).isFalse()
underTest.logOut()
- assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount)
+ assertThat(userRepository.logOutSecondaryUserCallCount)
+ .isEqualTo(secondaryUserLogoutCount)
+ assertThat(userRepository.logOutToSystemUserCallCount)
+ .isEqualTo(logoutToSystemUserCount)
}
}
@Test
- fun logOut_logsOut_whenAdminEnabledSecondaryLogout() {
+ fun logOut_logsOutSecondaryUser_whenAdminEnabledSecondaryLogout() {
testScope.runTest {
val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled)
val lastLogoutCount = userRepository.logOutSecondaryUserCallCount
+ val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount
userRepository.setSecondaryUserLogoutEnabled(true)
assertThat(isLogoutEnabled).isTrue()
underTest.logOut()
assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount + 1)
+ assertThat(userRepository.logOutToSystemUserCallCount)
+ .isEqualTo(logoutToSystemUserCount)
+ }
+ }
+
+ @Test
+ fun logOut_logsOutToSystemUser_whenLogoutToSystemUserIsEnabled() {
+ testScope.runTest {
+ val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled)
+ val lastLogoutCount = userRepository.logOutSecondaryUserCallCount
+ val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount
+ userRepository.setLogoutToSystemUserEnabled(true)
+ assertThat(isLogoutEnabled).isTrue()
+ underTest.logOut()
+ assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount)
+ assertThat(userRepository.logOutToSystemUserCallCount)
+ .isEqualTo(logoutToSystemUserCount + 1)
+ }
+ }
+
+ @Test
+ fun logOut_secondaryUserTakesPrecedence() {
+ testScope.runTest {
+ val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled)
+ val lastLogoutCount = userRepository.logOutSecondaryUserCallCount
+ val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount
+ userRepository.setLogoutToSystemUserEnabled(true)
+ userRepository.setSecondaryUserLogoutEnabled(true)
+ assertThat(isLogoutEnabled).isTrue()
+ underTest.logOut()
+ assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount + 1)
+ assertThat(userRepository.logOutToSystemUserCallCount)
+ .isEqualTo(logoutToSystemUserCount)
}
}
companion object {
private val USER_INFOS =
- listOf(UserInfo(0, "System user", 0), UserInfo(10, "Regular user", 0))
+ listOf(
+ UserInfo(0, "System user", 0),
+ UserInfo(10, "Regular user", 0),
+ UserInfo(11, "Secondary user", 0),
+ )
}
}
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 82c8c44..0854eb4 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -1086,4 +1086,9 @@
enable the desktop specific features.
-->
<bool name="config_enableDesktopFeatureSet">false</bool>
+
+ <!--
+ Whether the user switching can only happen by logging out and going through the system user (login screen).
+ -->
+ <bool name="config_userSwitchingMustGoThroughLoginScreen">false</bool>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index f20ce63..e9a33e0 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -23,11 +23,13 @@
import android.content.Context
import android.content.IntentFilter
import android.content.pm.UserInfo
+import android.content.res.Resources
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
import androidx.annotation.VisibleForTesting
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -109,6 +111,9 @@
/** Whether logout for secondary users is enabled by admin device policy. */
val isSecondaryUserLogoutEnabled: StateFlow<Boolean>
+ /** Whether logout into system user is enabled. */
+ val isLogoutToSystemUserEnabled: StateFlow<Boolean>
+
/** Asynchronously refresh the list of users. This will cause [userInfos] to be updated. */
fun refreshUsers()
@@ -121,6 +126,9 @@
/** Performs logout logout for secondary users. */
suspend fun logOutSecondaryUser()
+ /** Performs logout into the system user. */
+ suspend fun logOutToSystemUser()
+
/**
* Returns the user ID of the "main user" of the device. This user may have access to certain
* features which are limited to at most one user. There will never be more than one main user
@@ -143,6 +151,7 @@
@Inject
constructor(
@Application private val appContext: Context,
+ @Main private val resources: Resources,
private val manager: UserManager,
@Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
@@ -151,6 +160,7 @@
private val tracker: UserTracker,
private val devicePolicyManager: DevicePolicyManager,
private val broadcastDispatcher: BroadcastDispatcher,
+ private val statusBarService: IStatusBarService,
) : UserRepository {
private val _userSwitcherSettings: StateFlow<UserSwitcherSettingsModel> =
@@ -275,12 +285,34 @@
.stateIn(applicationScope, SharingStarted.Eagerly, false)
@SuppressLint("MissingPermission")
+ override val isLogoutToSystemUserEnabled: StateFlow<Boolean> =
+ selectedUser
+ .flatMapLatestConflated { selectedUser ->
+ if (selectedUser.isEligibleForLogout()) {
+ flowOf(
+ resources.getBoolean(R.bool.config_userSwitchingMustGoThroughLoginScreen)
+ )
+ } else {
+ flowOf(false)
+ }
+ }
+ .stateIn(applicationScope, SharingStarted.Eagerly, false)
+
+ @SuppressLint("MissingPermission")
override suspend fun logOutSecondaryUser() {
if (isSecondaryUserLogoutEnabled.value) {
withContext(backgroundDispatcher) { devicePolicyManager.logoutUser() }
}
}
+ override suspend fun logOutToSystemUser() {
+ // TODO(b/377493351) : start using proper logout API once it is available.
+ // Using reboot is a temporary solution.
+ if (isLogoutToSystemUserEnabled.value) {
+ withContext(backgroundDispatcher) { statusBarService.reboot(false) }
+ }
+ }
+
@SuppressLint("MissingPermission")
override fun refreshUsers() {
applicationScope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt
index 154f1dc..f2dd25f 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt
@@ -23,7 +23,10 @@
import com.android.systemui.user.data.repository.UserRepository
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
/** Encapsulates business logic to for the logout. */
@SysUISingleton
@@ -33,11 +36,22 @@
private val userRepository: UserRepository,
@Application private val applicationScope: CoroutineScope,
) {
- val isLogoutEnabled: StateFlow<Boolean> = userRepository.isSecondaryUserLogoutEnabled
+
+ val isLogoutEnabled: StateFlow<Boolean> =
+ combine(
+ userRepository.isSecondaryUserLogoutEnabled,
+ userRepository.isLogoutToSystemUserEnabled,
+ Boolean::or,
+ )
+ .stateIn(applicationScope, SharingStarted.Eagerly, false)
fun logOut() {
- if (userRepository.isSecondaryUserLogoutEnabled.value) {
- applicationScope.launch { userRepository.logOutSecondaryUser() }
+ applicationScope.launch {
+ if (userRepository.isSecondaryUserLogoutEnabled.value) {
+ userRepository.logOutSecondaryUser()
+ } else if (userRepository.isLogoutToSystemUserEnabled.value) {
+ userRepository.logOutToSystemUser()
+ }
}
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 1808a5f..85d582a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -72,6 +72,10 @@
override val isSecondaryUserLogoutEnabled: StateFlow<Boolean> =
_isSecondaryUserLogoutEnabled.asStateFlow()
+ private val _isLogoutToSystemUserEnabled = MutableStateFlow<Boolean>(false)
+ override val isLogoutToSystemUserEnabled: StateFlow<Boolean> =
+ _isLogoutToSystemUserEnabled.asStateFlow()
+
override var mainUserId: Int = MAIN_USER_ID
override var lastSelectedNonGuestUserId: Int = mainUserId
@@ -123,6 +127,17 @@
logOutSecondaryUserCallCount++
}
+ fun setLogoutToSystemUserEnabled(logoutEnabled: Boolean) {
+ _isLogoutToSystemUserEnabled.value = logoutEnabled
+ }
+
+ var logOutToSystemUserCallCount: Int = 0
+ private set
+
+ override suspend fun logOutToSystemUser() {
+ logOutToSystemUserCallCount++
+ }
+
fun setUserInfos(infos: List<UserInfo>) {
_userInfos.value = infos
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0826c53..18e8abb 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19307,31 +19307,18 @@
public void addCreatorToken(@Nullable Intent intent, String creatorPackage) {
if (!preventIntentRedirect()) return;
- if (intent == null || intent.getExtraIntentKeys() == null) return;
- for (String key : intent.getExtraIntentKeys()) {
- try {
- Intent extraIntent = intent.getParcelableExtra(key, Intent.class);
- if (extraIntent == null) {
- Slog.w(TAG, "The key {" + key
- + "} does not correspond to an intent in the extra bundle.");
- continue;
- }
- IntentCreatorToken creatorToken = createIntentCreatorToken(extraIntent,
- creatorPackage);
- if (creatorToken != null) {
- extraIntent.setCreatorToken(creatorToken);
- Slog.wtf(TAG, "A creator token is added to an intent. creatorPackage: "
- + creatorPackage + "; intent: " + intent);
- FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED,
- creatorToken.getCreatorUid());
- }
- } catch (Exception e) {
- Slog.wtf(TAG,
- "Something went wrong when trying to add creator token for embedded "
- + "intents of intent: ."
- + intent, e);
+ if (intent == null) return;
+ intent.forEachNestedCreatorToken(extraIntent -> {
+ IntentCreatorToken creatorToken = createIntentCreatorToken(extraIntent, creatorPackage);
+ if (creatorToken != null) {
+ extraIntent.setCreatorToken(creatorToken);
+ // TODO remove Slog.wtf once proven FrameworkStatsLog works. b/375396329
+ Slog.wtf(TAG, "A creator token is added to an intent. creatorPackage: "
+ + creatorPackage + "; intent: " + intent);
+ FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED,
+ creatorToken.getCreatorUid());
}
- }
+ });
}
private IntentCreatorToken createIntentCreatorToken(Intent intent, String creatorPackage) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index aa9ac6c..2eb9f3c 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -124,6 +124,7 @@
import com.android.server.Watchdog;
import com.android.server.net.BaseNetworkObserver;
import com.android.server.pm.UserManagerInternal;
+import com.android.server.power.feature.PowerManagerFlags;
import com.android.server.power.optimization.Flags;
import com.android.server.power.stats.BatteryExternalStatsWorker;
import com.android.server.power.stats.BatteryStatsDumpHelperImpl;
@@ -195,6 +196,7 @@
private final BatteryStats.BatteryStatsDumpHelper mDumpHelper;
private final PowerStatsUidResolver mPowerStatsUidResolver = new PowerStatsUidResolver();
private final PowerAttributor mPowerAttributor;
+ private final PowerManagerFlags mPowerManagerFlags = new PowerManagerFlags();
private volatile boolean mMonitorEnabled = true;
private boolean mRailsStatsCollectionEnabled = true;
@@ -617,6 +619,9 @@
BatteryConsumer.POWER_COMPONENT_ANY,
Flags.streamlinedMiscBatteryStats());
+ mStats.setMoveWscLoggingToNotifierEnabled(
+ mPowerManagerFlags.isMoveWscLoggingToNotifierEnabled());
+
mWorker.systemServicesReady();
mStats.systemServicesReady(mContext);
mCpuWakeupStats.systemServicesReady();
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 76e5ef0..0c04be1 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.service.dreams.Flags.cleanupDreamSettingsOnUninstall;
import static android.service.dreams.Flags.dreamHandlesBeingObscured;
import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID;
@@ -64,12 +65,15 @@
import android.service.dreams.DreamManagerInternal;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
+import android.text.TextUtils;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.Display;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.util.DumpUtils;
@@ -86,6 +90,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -155,6 +160,10 @@
private ComponentName mDreamOverlayServiceName;
private final AmbientDisplayConfiguration mDozeConfig;
+
+ /** Stores {@link PerUserPackageMonitor} to monitor dream uninstalls. */
+ private final SparseArray<PackageMonitor> mPackageMonitors = new SparseArray<>();
+
private final ActivityInterceptorCallback mActivityInterceptorCallback =
new ActivityInterceptorCallback() {
@Nullable
@@ -218,6 +227,15 @@
}
}
+ private final class PerUserPackageMonitor extends PackageMonitor {
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ super.onPackageRemoved(packageName, uid);
+ final int userId = getChangingUserId();
+ updateDreamOnPackageRemoved(packageName, userId);
+ }
+ }
+
public DreamManagerService(Context context) {
this(context, new DreamHandler(FgThread.get().getLooper()));
}
@@ -333,6 +351,37 @@
});
}
+ @Override
+ public void onUserStarting(@NonNull TargetUser user) {
+ super.onUserStarting(user);
+ if (cleanupDreamSettingsOnUninstall()) {
+ mHandler.post(() -> {
+ final int userId = user.getUserIdentifier();
+ if (!mPackageMonitors.contains(userId)) {
+ final PackageMonitor monitor = new PerUserPackageMonitor();
+ monitor.register(mContext, UserHandle.of(userId), mHandler);
+ mPackageMonitors.put(userId, monitor);
+ } else {
+ Slog.w(TAG, "Package monitor already registered for " + userId);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onUserStopping(@NonNull TargetUser user) {
+ super.onUserStopping(user);
+ if (cleanupDreamSettingsOnUninstall()) {
+ mHandler.post(() -> {
+ final PackageMonitor monitor = mPackageMonitors.removeReturnOld(
+ user.getUserIdentifier());
+ if (monitor != null) {
+ monitor.unregister();
+ }
+ });
+ }
+ }
+
private void dumpInternal(PrintWriter pw) {
synchronized (mLock) {
pw.println("DREAM MANAGER (dumpsys dreams)");
@@ -664,6 +713,30 @@
return validComponents.toArray(new ComponentName[validComponents.size()]);
}
+ private void updateDreamOnPackageRemoved(String packageName, int userId) {
+ final ComponentName[] componentNames = componentsFromString(
+ Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.SCREENSAVER_COMPONENTS,
+ userId));
+ if (componentNames != null) {
+ // Filter out any components in the removed package.
+ final ComponentName[] filteredComponents =
+ Arrays.stream(componentNames)
+ .filter((componentName -> !isSamePackage(packageName, componentName)))
+ .toArray(ComponentName[]::new);
+ if (filteredComponents.length != componentNames.length) {
+ setDreamComponentsForUser(userId, filteredComponents);
+ }
+ }
+ }
+
+ private static boolean isSamePackage(String packageName, ComponentName componentName) {
+ if (packageName == null || componentName == null) {
+ return false;
+ }
+ return TextUtils.equals(componentName.getPackageName(), packageName);
+ }
+
private void setDreamComponentsForUser(int userId, ComponentName[] componentNames) {
Settings.Secure.putStringForUser(mContext.getContentResolver(),
Settings.Secure.SCREENSAVER_COMPONENTS,
@@ -824,7 +897,10 @@
}
StringBuilder names = new StringBuilder();
for (ComponentName componentName : componentNames) {
- if (names.length() > 0) {
+ if (componentName == null) {
+ continue;
+ }
+ if (!names.isEmpty()) {
names.append(',');
}
names.append(componentName.flattenToString());
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index bf415a3..7505c71 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -646,9 +646,9 @@
int address = message.getSource();
int type = message.getParams()[2];
- if (!ActiveSource.of(address, path).equals(getActiveSource())) {
- HdmiLogger.debug("Check if a new device is connected to the active path");
- handleNewDeviceAtTheTailOfActivePath(path);
+ if (getActiveSource().logicalAddress != address && getActivePath() == path) {
+ HdmiLogger.debug("New logical address detected on the current active path.");
+ startRoutingControl(path, path, null);
}
startNewDeviceAction(ActiveSource.of(address, path), type);
return Constants.HANDLED;
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 132d6fa..0c5069f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -771,6 +771,14 @@
Slog.i(TAG, "Device does not support eARC.");
}
mHdmiCecNetwork = new HdmiCecNetwork(this, mCecController, mMhlController);
+ if (isTvDevice() && getWasCecDisabledOnStandbyByLowEnergyMode()) {
+ Slog.w(TAG, "Re-enable CEC on boot-up since it was disabled due to low energy "
+ + " mode.");
+ getHdmiCecConfig().setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+ HDMI_CEC_CONTROL_ENABLED);
+ setWasCecDisabledOnStandbyByLowEnergyMode(false);
+ setCecEnabled(HDMI_CEC_CONTROL_ENABLED);
+ }
if (isCecControlEnabled()) {
initializeCec(INITIATED_BY_BOOT_UP);
} else {
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 677a2de..028ac57 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -303,6 +303,8 @@
private final GnssPowerStatsCollector mGnssPowerStatsCollector;
private final CustomEnergyConsumerPowerStatsCollector mCustomEnergyConsumerPowerStatsCollector;
private final SparseBooleanArray mPowerStatsCollectorEnabled = new SparseBooleanArray();
+ private boolean mMoveWscLoggingToNotifierEnabled = false;
+
private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever =
new ScreenPowerStatsCollector.ScreenUsageTimeRetriever() {
@@ -5155,7 +5157,7 @@
Uid uidStats = getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs);
uidStats.noteStartWakeLocked(pid, name, type, elapsedRealtimeMs);
- if (!mPowerManagerFlags.isMoveWscLoggingToNotifierEnabled()) {
+ if (!mMoveWscLoggingToNotifierEnabled) {
mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name,
uidStats.mProcessState, true /* acquired */,
getPowerManagerWakeLockLevel(type));
@@ -5206,7 +5208,7 @@
Uid uidStats = getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs);
uidStats.noteStopWakeLocked(pid, name, type, elapsedRealtimeMs);
- if (!mPowerManagerFlags.isMoveWscLoggingToNotifierEnabled()) {
+ if (!mMoveWscLoggingToNotifierEnabled) {
mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name,
uidStats.mProcessState, false/* acquired */,
getPowerManagerWakeLockLevel(type));
@@ -15975,6 +15977,15 @@
}
}
+ /**
+ * Controls where the logging of the WakelockStateChanged atom occurs:
+ * true = Notifier, false = BatteryStatsImpl.
+ */
+ public void setMoveWscLoggingToNotifierEnabled(boolean enabled) {
+ synchronized (this) {
+ mMoveWscLoggingToNotifierEnabled = enabled;
+ }
+ }
@GuardedBy("this")
public void systemServicesReady(Context context) {
mConstants.startObserving(context.getContentResolver());
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 73ae51c..14be59f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -254,6 +254,7 @@
import static org.xmlpull.v1.XmlPullParser.END_TAG;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -10308,6 +10309,21 @@
if (pictureInPictureArgs != null && pictureInPictureArgs.hasSourceBoundsHint()) {
pictureInPictureArgs.getSourceRectHint().offset(windowBounds.left, windowBounds.top);
}
+
+ if (android.app.Flags.enableTvImplicitEnterPipRestriction()) {
+ PackageManager pm = mAtmService.mContext.getPackageManager();
+ if (pictureInPictureArgs.isAutoEnterEnabled()
+ && pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+ && pm.checkPermission(Manifest.permission.TV_IMPLICIT_ENTER_PIP, packageName)
+ == PackageManager.PERMISSION_DENIED) {
+ Log.i(TAG,
+ "Auto-enter PiP only allowed on TV if android.permission"
+ + ".TV_IMPLICIT_ENTER_PIP permission is held by the app.");
+ PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder();
+ builder.setAutoEnterEnabled(false);
+ pictureInPictureArgs.copyOnlySet(builder.build());
+ }
+ }
}
private void applyLocaleOverrideIfNeeded(Configuration resolvedConfig) {
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 2a825f3..dcbc234 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -1308,6 +1308,8 @@
Intent intent = new Intent();
Intent extraIntent = new Intent("EXTRA_INTENT_ACTION");
intent.putExtra("EXTRA_INTENT0", extraIntent);
+ Intent nestedIntent = new Intent("NESTED_INTENT_ACTION");
+ extraIntent.putExtra("NESTED_INTENT", nestedIntent);
intent.collectExtraIntentKeys();
mAms.addCreatorToken(intent, TEST_PACKAGE);
@@ -1317,6 +1319,11 @@
assertThat(token).isNotNull();
assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid());
assertThat(token.getCreatorPackage()).isEqualTo(TEST_PACKAGE);
+
+ token = (ActivityManagerService.IntentCreatorToken) nestedIntent.getCreatorToken();
+ assertThat(token).isNotNull();
+ assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid());
+ assertThat(token.getCreatorPackage()).isEqualTo(TEST_PACKAGE);
}
@Test
@@ -1349,6 +1356,8 @@
Intent intent = new Intent();
Intent extraIntent = new Intent("EXTRA_INTENT_ACTION");
intent.putExtra("EXTRA_INTENT", extraIntent);
+ Intent nestedIntent = new Intent("NESTED_INTENT_ACTION");
+ extraIntent.putExtra("NESTED_INTENT", nestedIntent);
intent.collectExtraIntentKeys();
@@ -1374,9 +1383,12 @@
extraIntent = intent.getParcelableExtra("EXTRA_INTENT", Intent.class);
extraIntent2 = intent.getParcelableExtra("EXTRA_INTENT2", Intent.class);
extraIntent3 = intent.getParcelableExtra("EXTRA_INTENT3", Intent.class);
+ nestedIntent = extraIntent.getParcelableExtra("NESTED_INTENT", Intent.class);
assertThat(extraIntent.getExtendedFlags()
& Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0);
+ assertThat(nestedIntent.getExtendedFlags()
+ & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0);
// sneaked in intent should have EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN set.
assertThat(extraIntent2.getExtendedFlags()
& Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isNotEqualTo(0);