Merge changes I7850ea5b,I9759b7dd
* changes:
Prepare PackageState/PackageUserState for API
Add runtime unmodifiable check for PackageState
diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml
index bb57161..4e24909 100644
--- a/apct-tests/perftests/core/AndroidManifest.xml
+++ b/apct-tests/perftests/core/AndroidManifest.xml
@@ -16,6 +16,7 @@
<application>
<uses-library android:name="android.test.runner" />
+ <profileable android:shell="true" />
<activity android:name="android.perftests.utils.PerfTestActivity"
android:exported="true">
<intent-filter>
diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
index 3452f58..6d1e6d0 100644
--- a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
+++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
@@ -28,16 +28,16 @@
import com.android.internal.util.ConcurrentUtils
import com.android.server.pm.parsing.pkg.PackageImpl
import com.android.server.pm.pkg.parsing.ParsingPackageUtils
+import java.io.File
+import java.io.FileOutputStream
+import java.util.concurrent.ArrayBlockingQueue
+import java.util.concurrent.TimeUnit
import libcore.io.IoUtils
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-import java.io.File
-import java.io.FileOutputStream
-import java.util.concurrent.ArrayBlockingQueue
-import java.util.concurrent.TimeUnit
@LargeTest
@RunWith(Parameterized::class)
@@ -180,8 +180,8 @@
protected abstract fun parseImpl(file: File): PackageType
}
- class ParallelParser1(private val cacher: PackageCacher1? = null)
- : ParallelParser<PackageParser.Package>(cacher) {
+ class ParallelParser1(private val cacher: PackageCacher1? = null) :
+ ParallelParser<PackageParser.Package>(cacher) {
val parser = PackageParser().apply {
setCallback { true }
}
@@ -189,8 +189,8 @@
override fun parseImpl(file: File) = parser.parsePackage(file, 0, cacher != null)
}
- class ParallelParser2(cacher: PackageCacher2? = null)
- : ParallelParser<PackageImpl>(cacher) {
+ class ParallelParser2(cacher: PackageCacher2? = null) :
+ ParallelParser<PackageImpl>(cacher) {
val input = ThreadLocal.withInitial {
// For testing, just disable enforcement to avoid hooking up to compat framework
ParseTypeImpl(ParseInput.Callback { _, _, _ -> false })
@@ -218,7 +218,7 @@
})
override fun parseImpl(file: File) =
- parser.parsePackage(input.get()!!.reset(), file, 0, null).result
+ parser.parsePackage(input.get()!!.reset(), file, 0).result
as PackageImpl
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 4d5eef2..1e13dbf 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1404,8 +1404,31 @@
}
mChangedJobList.remove(cancelled);
// Cancel if running.
- mConcurrencyManager.stopJobOnServiceContextLocked(
+ final boolean wasRunning = mConcurrencyManager.stopJobOnServiceContextLocked(
cancelled, reason, internalReasonCode, debugReason);
+ // If the job was running, the JobServiceContext should log with state FINISHED.
+ if (!wasRunning) {
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
+ cancelled.getSourceUid(), null, cancelled.getBatteryName(),
+ FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__CANCELLED,
+ internalReasonCode, cancelled.getStandbyBucket(),
+ cancelled.getJobId(),
+ cancelled.hasChargingConstraint(),
+ cancelled.hasBatteryNotLowConstraint(),
+ cancelled.hasStorageNotLowConstraint(),
+ cancelled.hasTimingDelayConstraint(),
+ cancelled.hasDeadlineConstraint(),
+ cancelled.hasIdleConstraint(),
+ cancelled.hasConnectivityConstraint(),
+ cancelled.hasContentTriggerConstraint(),
+ cancelled.isRequestedExpeditedJob(),
+ /* isRunningAsExpeditedJob */ false,
+ reason,
+ cancelled.getJob().isPrefetch(),
+ cancelled.getJob().getPriority(),
+ cancelled.getEffectivePriority(),
+ cancelled.getNumFailures());
+ }
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
if (DEBUG) Slog.i(TAG, "Tracking replacement job " + incomingJob.toShortString());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index dfa1442..fcfb45c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -742,6 +742,10 @@
}
} catch (XmlPullParserException | IOException e) {
Slog.wtf(TAG, "Error jobstore xml.", e);
+ } catch (Exception e) {
+ // Crashing at this point would result in a boot loop, so live with a general
+ // Exception for system stability's sake.
+ Slog.wtf(TAG, "Unexpected exception", e);
} finally {
if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once.
mPersistInfo.countAllJobsLoaded = numJobs;
@@ -890,6 +894,9 @@
} catch (IOException e) {
Slog.d(TAG, "Error I/O Exception.", e);
return null;
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Constraints contained invalid data", e);
+ return null;
}
parser.next(); // Consume </constraints>
@@ -986,8 +993,14 @@
return null;
}
- PersistableBundle extras = PersistableBundle.restoreFromXml(parser);
- jobBuilder.setExtras(extras);
+ final PersistableBundle extras;
+ try {
+ extras = PersistableBundle.restoreFromXml(parser);
+ jobBuilder.setExtras(extras);
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Persisted extras contained invalid data", e);
+ return null;
+ }
parser.nextTag(); // Consume </extras>
final JobInfo builtJob;
diff --git a/api/Android.mk b/api/Android.mk
new file mode 100644
index 0000000..ce5f995
--- /dev/null
+++ b/api/Android.mk
@@ -0,0 +1,2 @@
+.PHONY: checkapi
+checkapi: frameworks-base-api-current-compat frameworks-base-api-system-current-compat frameworks-base-api-module-lib-current-compat
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index 4efaeea..444f91d 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -222,7 +222,7 @@
|| !Read32(stream, &entry_count)) {
return nullptr;
}
- target_inline_entries.emplace_back(std::make_tuple(target_entry, entry_offset, entry_count));
+ target_inline_entries.emplace_back(target_entry, entry_offset, entry_count);
}
// Read the inline overlay resource values
@@ -241,7 +241,7 @@
|| !Read32(stream, &value.data_value)) {
return nullptr;
}
- target_values.emplace_back(std::make_pair(config_index, value));
+ target_values.emplace_back(config_index, value);
}
// Read the configurations
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7fd4283..7dcfab4 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -10056,7 +10056,7 @@
method @WorkerThread public long getAllocatableBytes(@NonNull java.util.UUID, @RequiresPermission int) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public int getExternalStorageMountMode(int, @NonNull String);
method public static boolean hasIsolatedStorage();
- method public void updateExternalStorageFileQuotaType(@NonNull java.io.File, int) throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) public void updateExternalStorageFileQuotaType(@NonNull java.io.File, int) throws java.io.IOException;
field @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1
field public static final int MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE = 4; // 0x4
field public static final int MOUNT_MODE_EXTERNAL_DEFAULT = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 887af1f..99005a4 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1168,9 +1168,11 @@
package android.hardware.devicestate {
public final class DeviceStateManager {
+ method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelBaseStateOverride();
method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelStateRequest();
method @NonNull public int[] getSupportedStates();
method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
+ method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestBaseStateOverride(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
method public void unregisterCallback(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
field public static final int MAXIMUM_DEVICE_STATE = 255; // 0xff
@@ -1221,6 +1223,7 @@
method @Nullable public android.view.Display.Mode getGlobalUserPreferredDisplayMode();
method @NonNull public int[] getUserDisabledHdrTypes();
method public boolean isMinimalPostProcessingRequested(int);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public void overrideHdrTypes(int, @NonNull int[]);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAreUserDisabledHdrTypesAllowed(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setGlobalUserPreferredDisplayMode(@NonNull android.view.Display.Mode);
method @RequiresPermission(android.Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE) public void setRefreshRateSwitchingType(int);
@@ -2883,7 +2886,6 @@
ctor public SurfaceControl(@NonNull android.view.SurfaceControl, @NonNull String);
method @NonNull public static android.os.IBinder getInternalDisplayToken();
method public boolean isSameSurface(@NonNull android.view.SurfaceControl);
- method public static void overrideHdrTypes(@NonNull android.os.IBinder, @NonNull int[]);
}
public class SurfaceControlViewHost {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 80121b7..0e1b47f 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1094,7 +1094,7 @@
&& (options == null
|| ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) {
throw new AndroidRuntimeException(
- "Calling startActivity() from outside of an Activity "
+ "Calling startActivity() from outside of an Activity"
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
@@ -1128,7 +1128,7 @@
public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
- "Calling startActivities() from outside of an Activity "
+ "Calling startActivities() from outside of an Activity"
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent."
+ " Is this really what you want?");
}
@@ -1142,7 +1142,7 @@
warnIfCallingFromSystemProcess();
if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
- "Calling startActivities() from outside of an Activity "
+ "Calling startActivities() from outside of an Activity"
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent."
+ " Is this really what you want?");
}
diff --git a/core/java/android/app/UidObserver.java b/core/java/android/app/UidObserver.java
new file mode 100644
index 0000000..9e92807
--- /dev/null
+++ b/core/java/android/app/UidObserver.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+/**
+ * Default implementation of {@link IUidObserver} for which all methods are
+ * no-op. Subclasses can override the select methods they're interested in
+ * handling.
+ *
+ * @hide
+ */
+public class UidObserver extends IUidObserver.Stub {
+ @Override
+ public void onUidActive(int uid) {
+ }
+
+ @Override
+ public void onUidCachedChanged(int uid, boolean cached) {
+ }
+
+ @Override
+ public void onUidGone(int uid, boolean disabled) {
+ }
+
+ @Override
+ public void onUidIdle(int uid, boolean disabled) {
+ }
+
+ @Override
+ public void onUidProcAdjChanged(int uid) {
+ }
+
+ @Override
+ public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
+ }
+}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 10d6f2d..64fed63 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -78,7 +78,6 @@
public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
private static final int PARSE_IS_SYSTEM_DIR = 1 << 4;
private static final int PARSE_COLLECT_CERTIFICATES = 1 << 5;
- private static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
private static final String TAG_APPLICATION = "application";
private static final String TAG_PACKAGE_VERIFIER = "package-verifier";
private static final String TAG_PROFILEABLE = "profileable";
@@ -103,7 +102,7 @@
public static ParseResult<PackageLite> parsePackageLite(ParseInput input,
File packageFile, int flags) {
if (packageFile.isDirectory()) {
- return parseClusterPackageLite(input, packageFile, /* frameworkSplits= */ null, flags);
+ return parseClusterPackageLite(input, packageFile, flags);
} else {
return parseMonolithicPackageLite(input, packageFile, flags);
}
@@ -137,38 +136,19 @@
/**
* Parse lightweight details about a directory of APKs.
*
- * @param packageDirOrApk is the folder that contains split apks for a regular app or the
- * framework-res.apk for framwork-res splits (in which case the
- * splits come in the <code>frameworkSplits</code> parameter)
+ * @param packageDir is the folder that contains split apks for a regular app
*/
public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input,
- File packageDirOrApk, List<File> frameworkSplits, int flags) {
+ File packageDir, int flags) {
final File[] files;
- final boolean parsingFrameworkSplits = (flags & PARSE_FRAMEWORK_RES_SPLITS) != 0;
- if (parsingFrameworkSplits) {
- if (ArrayUtils.isEmpty(frameworkSplits)) {
- return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
- "No packages found in split");
- }
- files = frameworkSplits.toArray(new File[frameworkSplits.size() + 1]);
- // we also want to process the base apk so add it to the array
- files[files.length - 1] = packageDirOrApk;
- } else {
- files = packageDirOrApk.listFiles();
- if (ArrayUtils.isEmpty(files)) {
- return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
- "No packages found in split");
- }
- // Apk directory is directly nested under the current directory
- if (files.length == 1 && files[0].isDirectory()) {
- return parseClusterPackageLite(input, files[0], frameworkSplits, flags);
- }
+ files = packageDir.listFiles();
+ if (ArrayUtils.isEmpty(files)) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
+ "No packages found in split");
}
-
- if (parsingFrameworkSplits) {
- // disable the flag for checking the certificates of the splits. We know they
- // won't match, but we rely on the mainline apex to be safe if it was installed
- flags = flags & ~PARSE_COLLECT_CERTIFICATES;
+ // Apk directory is directly nested under the current directory
+ if (files.length == 1 && files[0].isDirectory()) {
+ return parseClusterPackageLite(input, files[0], flags);
}
String packageName = null;
@@ -186,10 +166,6 @@
}
final ApkLite lite = result.getResult();
- if (parsingFrameworkSplits && file == files[files.length - 1]) {
- baseApk = lite;
- break;
- }
// Assert that all package names and version codes are
// consistent with the first one we encounter.
if (packageName == null) {
@@ -201,8 +177,7 @@
"Inconsistent package " + lite.getPackageName() + " in " + file
+ "; expected " + packageName);
}
- // we allow version codes that do not match for framework splits
- if (!parsingFrameworkSplits && versionCode != lite.getVersionCode()) {
+ if (versionCode != lite.getVersionCode()) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Inconsistent version " + lite.getVersionCode() + " in " + file
+ "; expected " + versionCode);
@@ -217,15 +192,11 @@
}
}
}
- // baseApk is set in the last iteration of the for each loop when we are parsing
- // frameworkRes splits or needs to be done now otherwise
- if (!parsingFrameworkSplits) {
- baseApk = apks.remove(null);
- }
+ baseApk = apks.remove(null);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- return composePackageLiteFromApks(input, packageDirOrApk, baseApk, apks);
+ return composePackageLiteFromApks(input, packageDir, baseApk, apks);
}
/**
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index 30aa4db..bdd45e6 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -16,6 +16,7 @@
package android.hardware.devicestate;
+import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -115,6 +116,52 @@
}
/**
+ * Submits a {@link DeviceStateRequest request} to override the base state of the device. This
+ * should only be used for testing, where you want to simulate the physical change to the
+ * device state.
+ * <p>
+ * By default, the request is kept active until one of the following occurs:
+ * <ul>
+ * <li>The physical state of the device changes</li>
+ * <li>The system deems the request can no longer be honored, for example if the requested
+ * state becomes unsupported.
+ * <li>A call to {@link #cancelBaseStateOverride}.
+ * <li>Another processes submits a request succeeding this request in which case the request
+ * will be canceled.
+ * </ul>
+ *
+ * Submitting a base state override request may not cause any change in the presentation
+ * of the system if there is an emulated request made through {@link #requestState}, as the
+ * emulated override requests take priority.
+ *
+ * @throws IllegalArgumentException if the requested state is unsupported.
+ * @throws SecurityException if the caller does not hold the
+ * {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission.
+ *
+ * @see DeviceStateRequest
+ */
+ @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
+ public void requestBaseStateOverride(@NonNull DeviceStateRequest request,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable DeviceStateRequest.Callback callback) {
+ mGlobal.requestBaseStateOverride(request, executor, callback);
+ }
+
+ /**
+ * Cancels the active {@link DeviceStateRequest} previously submitted with a call to
+ * {@link #requestBaseStateOverride(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
+ * <p>
+ * This method is noop if there is no base state request currently active.
+ *
+ * @throws SecurityException if the caller does not hold the
+ * {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission.
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DEVICE_STATE)
+ public void cancelBaseStateOverride() {
+ mGlobal.cancelBaseStateOverride();
+ }
+
+ /**
* Registers a callback to receive notifications about changes in device state.
*
* @param executor the executor to process notifications.
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index aba538f..738045d 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
import android.os.Binder;
@@ -81,6 +82,7 @@
@VisibleForTesting
public DeviceStateManagerGlobal(@NonNull IDeviceStateManager deviceStateManager) {
mDeviceStateManager = deviceStateManager;
+ registerCallbackIfNeededLocked();
}
/**
@@ -116,27 +118,22 @@
* DeviceStateRequest.Callback)
* @see DeviceStateRequest
*/
+ @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
+ conditional = true)
public void requestState(@NonNull DeviceStateRequest request,
@Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) {
- if (callback == null && executor != null) {
- throw new IllegalArgumentException("Callback must be supplied with executor.");
- } else if (executor == null && callback != null) {
- throw new IllegalArgumentException("Executor must be supplied with callback.");
- }
-
+ DeviceStateRequestWrapper requestWrapper = new DeviceStateRequestWrapper(request, callback,
+ executor);
synchronized (mLock) {
- registerCallbackIfNeededLocked();
-
if (findRequestTokenLocked(request) != null) {
// This request has already been submitted.
return;
}
-
// Add the request wrapper to the mRequests array before requesting the state as the
// callback could be triggered immediately if the mDeviceStateManager IBinder is in the
// same process as this instance.
IBinder token = new Binder();
- mRequests.put(token, new DeviceStateRequestWrapper(request, callback, executor));
+ mRequests.put(token, requestWrapper);
try {
mDeviceStateManager.requestState(token, request.getState(), request.getFlags());
@@ -153,10 +150,10 @@
*
* @see DeviceStateManager#cancelStateRequest
*/
+ @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
+ conditional = true)
public void cancelStateRequest() {
synchronized (mLock) {
- registerCallbackIfNeededLocked();
-
try {
mDeviceStateManager.cancelStateRequest();
} catch (RemoteException ex) {
@@ -166,6 +163,56 @@
}
/**
+ * Submits a {@link DeviceStateRequest request} to modify the base state of the device.
+ *
+ * @see DeviceStateManager#requestBaseStateOverride(DeviceStateRequest, Executor,
+ * DeviceStateRequest.Callback)
+ * @see DeviceStateRequest
+ */
+ @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
+ public void requestBaseStateOverride(@NonNull DeviceStateRequest request,
+ @Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) {
+ DeviceStateRequestWrapper requestWrapper = new DeviceStateRequestWrapper(request, callback,
+ executor);
+ synchronized (mLock) {
+ if (findRequestTokenLocked(request) != null) {
+ // This request has already been submitted.
+ return;
+ }
+ // Add the request wrapper to the mRequests array before requesting the state as the
+ // callback could be triggered immediately if the mDeviceStateManager IBinder is in the
+ // same process as this instance.
+ IBinder token = new Binder();
+ mRequests.put(token, requestWrapper);
+
+ try {
+ mDeviceStateManager.requestBaseStateOverride(token, request.getState(),
+ request.getFlags());
+ } catch (RemoteException ex) {
+ mRequests.remove(token);
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Cancels a {@link DeviceStateRequest request} previously submitted with a call to
+ * {@link #requestBaseStateOverride(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
+ *
+ * @see DeviceStateManager#cancelBaseStateOverride
+ */
+ @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
+ public void cancelBaseStateOverride() {
+ synchronized (mLock) {
+ try {
+ mDeviceStateManager.cancelBaseStateOverride();
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Registers a callback to receive notifications about changes in device state.
*
* @see DeviceStateManager#registerCallback(Executor, DeviceStateCallback)
@@ -179,9 +226,6 @@
// This callback is already registered.
return;
}
-
- registerCallbackIfNeededLocked();
-
// Add the callback wrapper to the mCallbacks array after registering the callback as
// the callback could be triggered immediately if the mDeviceStateManager IBinder is in
// the same process as this instance.
@@ -357,6 +401,8 @@
DeviceStateRequestWrapper(@NonNull DeviceStateRequest request,
@Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) {
+ validateRequestWrapperParameters(callback, executor);
+
mRequest = request;
mCallback = callback;
mExecutor = executor;
@@ -377,5 +423,14 @@
mExecutor.execute(() -> mCallback.onRequestCanceled(mRequest));
}
+
+ private void validateRequestWrapperParameters(
+ @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) {
+ if (callback == null && executor != null) {
+ throw new IllegalArgumentException("Callback must be supplied with executor.");
+ } else if (executor == null && callback != null) {
+ throw new IllegalArgumentException("Executor must be supplied with callback.");
+ }
+ }
}
}
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
index e450e42..7175eae 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
@@ -41,6 +41,10 @@
* previously registered with {@link #registerCallback(IDeviceStateManagerCallback)} before a
* call to this method.
*
+ * Requesting a state does not cancel a base state override made through
+ * {@link #requestBaseStateOverride}, but will still attempt to put the device into the
+ * supplied {@code state}.
+ *
* @param token the request token provided
* @param state the state of device the request is asking for
* @param flags any flags that correspond to the request
@@ -50,14 +54,53 @@
* @throws IllegalStateException if the supplied {@code token} has already been registered.
* @throws IllegalArgumentException if the supplied {@code state} is not supported.
*/
+ @JavaPassthrough(annotation=
+ "@android.annotation.RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true)")
void requestState(IBinder token, int state, int flags);
/**
* Cancels the active request previously submitted with a call to
- * {@link #requestState(IBinder, int, int)}.
+ * {@link #requestState(IBinder, int, int)}. Will have no effect on any base state override that
+ * was previously requested with {@link #requestBaseStateOverride}.
*
* @throws IllegalStateException if a callback has not yet been registered for the calling
* process.
*/
+ @JavaPassthrough(annotation=
+ "@android.annotation.RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true)")
void cancelStateRequest();
+
+ /**
+ * Requests that the device's base state be overridden to the supplied {@code state}. A callback
+ * <b>MUST</b> have been previously registered with
+ * {@link #registerCallback(IDeviceStateManagerCallback)} before a call to this method.
+ *
+ * This method should only be used for testing, when you want to simulate the device physically
+ * changing states. If you are looking to change device state for a feature, where the system
+ * should still be aware that the physical state is different than the emulated state, use
+ * {@link #requestState}.
+ *
+ * @param token the request token provided
+ * @param state the state of device the request is asking for
+ * @param flags any flags that correspond to the request
+ *
+ * @throws IllegalStateException if a callback has not yet been registered for the calling
+ * process.
+ * @throws IllegalStateException if the supplied {@code token} has already been registered.
+ * @throws IllegalArgumentException if the supplied {@code state} is not supported.
+ */
+ @JavaPassthrough(annotation=
+ "@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)")
+ void requestBaseStateOverride(IBinder token, int state, int flags);
+
+ /**
+ * Cancels the active base state request previously submitted with a call to
+ * {@link #overrideBaseState(IBinder, int, int)}.
+ *
+ * @throws IllegalStateException if a callback has not yet been registered for the calling
+ * process.
+ */
+ @JavaPassthrough(annotation=
+ "@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)")
+ void cancelBaseStateOverride();
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 8bc11cb..dfb4236 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -855,6 +855,16 @@
return mGlobal.getUserDisabledHdrTypes();
}
+ /**
+ * Overrides HDR modes for a display device.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER)
+ @TestApi
+ public void overrideHdrTypes(int displayId, @NonNull int[] modes) {
+ mGlobal.overrideHdrTypes(displayId, modes);
+ }
/**
* Creates a virtual display.
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index e2f8592..9294dea 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -20,9 +20,11 @@
import static android.hardware.display.DisplayManager.EventsMask;
import static android.view.Display.HdrCapabilities.HdrType;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.PropertyInvalidatedCache;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -577,6 +579,20 @@
}
}
+ /**
+ * Overrides HDR modes for a display device.
+ *
+ */
+ @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER)
+ public void overrideHdrTypes(int displayId, int[] modes) {
+ try {
+ mDm.overrideHdrTypes(displayId, modes);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+
public void requestColorMode(int displayId, int colorMode) {
try {
mDm.requestColorMode(displayId, colorMode);
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index d57a272..00bccc6 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -28,6 +28,7 @@
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
+import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.window.DisplayWindowPolicyController;
import android.window.ScreenCapture;
@@ -398,6 +399,11 @@
public abstract DisplayWindowPolicyController getDisplayWindowPolicyController(int displayId);
/**
+ * Get DisplayPrimaries from SF for a particular display.
+ */
+ public abstract SurfaceControl.DisplayPrimaries getDisplayNativePrimaries(int displayId);
+
+ /**
* Describes the requested power state of the display.
*
* This object is intended to describe the general characteristics of the
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index fa3c450..b166e21 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -83,6 +83,9 @@
// No permissions required.
int[] getUserDisabledHdrTypes();
+ // Requires ACCESS_SURFACE_FLINGER permission.
+ void overrideHdrTypes(int displayId, in int[] modes);
+
// Requires CONFIGURE_DISPLAY_COLOR_MODE
void requestColorMode(int displayId, int colorMode);
diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java
index aa5480a..4a18333 100644
--- a/core/java/android/hardware/radio/TunerAdapter.java
+++ b/core/java/android/hardware/radio/TunerAdapter.java
@@ -16,12 +16,13 @@
package android.hardware.radio;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Bitmap;
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -29,28 +30,36 @@
/**
* Implements the RadioTuner interface by forwarding calls to radio service.
*/
-class TunerAdapter extends RadioTuner {
+final class TunerAdapter extends RadioTuner {
private static final String TAG = "BroadcastRadio.TunerAdapter";
- @NonNull private final ITuner mTuner;
- @NonNull private final TunerCallbackAdapter mCallback;
- private boolean mIsClosed = false;
+ private final ITuner mTuner;
+ private final TunerCallbackAdapter mCallback;
+ private final Object mLock = new Object();
- private @RadioManager.Band int mBand;
+ @GuardedBy("mLock")
+ private boolean mIsClosed;
+ @GuardedBy("mLock")
+ @RadioManager.Band
+ private int mBand;
+
+ @GuardedBy("mLock")
private ProgramList mLegacyListProxy;
+
+ @GuardedBy("mLock")
private Map<String, String> mLegacyListFilter;
- TunerAdapter(@NonNull ITuner tuner, @NonNull TunerCallbackAdapter callback,
+ TunerAdapter(ITuner tuner, TunerCallbackAdapter callback,
@RadioManager.Band int band) {
- mTuner = Objects.requireNonNull(tuner);
- mCallback = Objects.requireNonNull(callback);
+ mTuner = Objects.requireNonNull(tuner, "Tuner cannot be null");
+ mCallback = Objects.requireNonNull(callback, "Callback cannot be null");
mBand = band;
}
@Override
public void close() {
- synchronized (mTuner) {
+ synchronized (mLock) {
if (mIsClosed) {
Log.v(TAG, "Tuner is already closed");
return;
@@ -60,8 +69,8 @@
mLegacyListProxy.close();
mLegacyListProxy = null;
}
- mCallback.close();
}
+ mCallback.close();
try {
mTuner.close();
} catch (RemoteException e) {
@@ -71,16 +80,20 @@
@Override
public int setConfiguration(RadioManager.BandConfig config) {
- if (config == null) return RadioManager.STATUS_BAD_VALUE;
+ if (config == null) {
+ return RadioManager.STATUS_BAD_VALUE;
+ }
try {
mTuner.setConfiguration(config);
- mBand = config.getType();
+ synchronized (mLock) {
+ mBand = config.getType();
+ }
return RadioManager.STATUS_OK;
} catch (IllegalArgumentException e) {
Log.e(TAG, "Can't set configuration", e);
return RadioManager.STATUS_BAD_VALUE;
} catch (RemoteException e) {
- Log.e(TAG, "service died", e);
+ Log.e(TAG, "Service died", e);
return RadioManager.STATUS_DEAD_OBJECT;
}
}
@@ -94,7 +107,7 @@
config[0] = mTuner.getConfiguration();
return RadioManager.STATUS_OK;
} catch (RemoteException e) {
- Log.e(TAG, "service died", e);
+ Log.e(TAG, "Service died", e);
return RadioManager.STATUS_DEAD_OBJECT;
}
}
@@ -107,7 +120,7 @@
Log.e(TAG, "Can't set muted", e);
return RadioManager.STATUS_ERROR;
} catch (RemoteException e) {
- Log.e(TAG, "service died", e);
+ Log.e(TAG, "Service died", e);
return RadioManager.STATUS_DEAD_OBJECT;
}
return RadioManager.STATUS_OK;
@@ -118,7 +131,7 @@
try {
return mTuner.isMuted();
} catch (RemoteException e) {
- Log.e(TAG, "service died", e);
+ Log.e(TAG, "Service died", e);
return true;
}
}
@@ -126,12 +139,13 @@
@Override
public int step(int direction, boolean skipSubChannel) {
try {
- mTuner.step(direction == RadioTuner.DIRECTION_DOWN, skipSubChannel);
+ mTuner.step(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN,
+ skipSubChannel);
} catch (IllegalStateException e) {
Log.e(TAG, "Can't step", e);
return RadioManager.STATUS_INVALID_OPERATION;
} catch (RemoteException e) {
- Log.e(TAG, "service died", e);
+ Log.e(TAG, "Service died", e);
return RadioManager.STATUS_DEAD_OBJECT;
}
return RadioManager.STATUS_OK;
@@ -140,12 +154,13 @@
@Override
public int scan(int direction, boolean skipSubChannel) {
try {
- mTuner.scan(direction == RadioTuner.DIRECTION_DOWN, skipSubChannel);
+ mTuner.scan(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN,
+ skipSubChannel);
} catch (IllegalStateException e) {
Log.e(TAG, "Can't scan", e);
return RadioManager.STATUS_INVALID_OPERATION;
} catch (RemoteException e) {
- Log.e(TAG, "service died", e);
+ Log.e(TAG, "Service died", e);
return RadioManager.STATUS_DEAD_OBJECT;
}
return RadioManager.STATUS_OK;
@@ -154,7 +169,11 @@
@Override
public int tune(int channel, int subChannel) {
try {
- mTuner.tune(ProgramSelector.createAmFmSelector(mBand, channel, subChannel));
+ int band;
+ synchronized (mLock) {
+ band = mBand;
+ }
+ mTuner.tune(ProgramSelector.createAmFmSelector(band, channel, subChannel));
} catch (IllegalStateException e) {
Log.e(TAG, "Can't tune", e);
return RadioManager.STATUS_INVALID_OPERATION;
@@ -162,18 +181,18 @@
Log.e(TAG, "Can't tune", e);
return RadioManager.STATUS_BAD_VALUE;
} catch (RemoteException e) {
- Log.e(TAG, "service died", e);
+ Log.e(TAG, "Service died", e);
return RadioManager.STATUS_DEAD_OBJECT;
}
return RadioManager.STATUS_OK;
}
@Override
- public void tune(@NonNull ProgramSelector selector) {
+ public void tune(ProgramSelector selector) {
try {
mTuner.tune(selector);
} catch (RemoteException e) {
- throw new RuntimeException("service died", e);
+ throw new RuntimeException("Service died", e);
}
}
@@ -185,7 +204,7 @@
Log.e(TAG, "Can't cancel", e);
return RadioManager.STATUS_INVALID_OPERATION;
} catch (RemoteException e) {
- Log.e(TAG, "service died", e);
+ Log.e(TAG, "Service died", e);
return RadioManager.STATUS_DEAD_OBJECT;
}
return RadioManager.STATUS_OK;
@@ -196,7 +215,7 @@
try {
mTuner.cancelAnnouncement();
} catch (RemoteException e) {
- throw new RuntimeException("service died", e);
+ throw new RuntimeException("Service died", e);
}
}
@@ -217,11 +236,12 @@
}
@Override
- public @Nullable Bitmap getMetadataImage(int id) {
+ @Nullable
+ public Bitmap getMetadataImage(int id) {
try {
return mTuner.getImage(id);
} catch (RemoteException e) {
- throw new RuntimeException("service died", e);
+ throw new RuntimeException("Service died", e);
}
}
@@ -230,66 +250,72 @@
try {
return mTuner.startBackgroundScan();
} catch (RemoteException e) {
- throw new RuntimeException("service died", e);
+ throw new RuntimeException("Service died", e);
}
}
@Override
- public @NonNull List<RadioManager.ProgramInfo>
+ public List<RadioManager.ProgramInfo>
getProgramList(@Nullable Map<String, String> vendorFilter) {
- synchronized (mTuner) {
+ synchronized (mLock) {
if (mLegacyListProxy == null || !Objects.equals(mLegacyListFilter, vendorFilter)) {
Log.i(TAG, "Program list filter has changed, requesting new list");
mLegacyListProxy = new ProgramList();
mLegacyListFilter = vendorFilter;
-
mCallback.clearLastCompleteList();
- mCallback.setProgramListObserver(mLegacyListProxy, () -> { });
- try {
- mTuner.startProgramListUpdates(new ProgramList.Filter(vendorFilter));
- } catch (RemoteException ex) {
- throw new RuntimeException("service died", ex);
- }
+ mCallback.setProgramListObserver(mLegacyListProxy, () -> {
+ Log.i(TAG, "Empty closeListener in programListObserver");
+ });
}
-
- List<RadioManager.ProgramInfo> list = mCallback.getLastCompleteList();
- if (list == null) throw new IllegalStateException("Program list is not ready yet");
- return list;
}
+ try {
+ mTuner.startProgramListUpdates(new ProgramList.Filter(vendorFilter));
+ } catch (RemoteException ex) {
+ throw new RuntimeException("Service died", ex);
+ }
+
+ List<RadioManager.ProgramInfo> list = mCallback.getLastCompleteList();
+ if (list == null) {
+ throw new IllegalStateException("Program list is not ready yet");
+ }
+ return list;
}
@Override
- public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) {
- synchronized (mTuner) {
+ @Nullable
+ public ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) {
+ synchronized (mLock) {
if (mLegacyListProxy != null) {
mLegacyListProxy.close();
mLegacyListProxy = null;
}
mLegacyListFilter = null;
-
- ProgramList list = new ProgramList();
- mCallback.setProgramListObserver(list, () -> {
- try {
- mTuner.stopProgramListUpdates();
- } catch (IllegalStateException ex) {
- // it's fine to not stop updates if tuner is already closed
- } catch (RemoteException ex) {
- Log.e(TAG, "Couldn't stop program list updates", ex);
- }
- });
-
- try {
- mTuner.startProgramListUpdates(filter);
- } catch (UnsupportedOperationException ex) {
- Log.i(TAG, "Program list is not supported with this hardware");
- return null;
- } catch (RemoteException ex) {
- mCallback.setProgramListObserver(null, () -> { });
- throw new RuntimeException("service died", ex);
- }
-
- return list;
}
+ ProgramList list = new ProgramList();
+ mCallback.setProgramListObserver(list, () -> {
+ try {
+ mTuner.stopProgramListUpdates();
+ } catch (IllegalStateException ex) {
+ // it's fine to not stop updates if tuner is already closed
+ Log.e(TAG, "Tuner may already be closed", ex);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Couldn't stop program list updates", ex);
+ }
+ });
+
+ try {
+ mTuner.startProgramListUpdates(filter);
+ } catch (UnsupportedOperationException ex) {
+ Log.i(TAG, "Program list is not supported with this hardware");
+ return null;
+ } catch (RemoteException ex) {
+ mCallback.setProgramListObserver(null, () -> {
+ Log.i(TAG, "Empty closeListener in programListObserver");
+ });
+ throw new RuntimeException("Service died", ex);
+ }
+
+ return list;
}
@Override
@@ -315,7 +341,7 @@
try {
return mTuner.isConfigFlagSupported(flag);
} catch (RemoteException e) {
- throw new RuntimeException("service died", e);
+ throw new RuntimeException("Service died", e);
}
}
@@ -324,7 +350,7 @@
try {
return mTuner.isConfigFlagSet(flag);
} catch (RemoteException e) {
- throw new RuntimeException("service died", e);
+ throw new RuntimeException("Service died", e);
}
}
@@ -333,25 +359,26 @@
try {
mTuner.setConfigFlag(flag, value);
} catch (RemoteException e) {
- throw new RuntimeException("service died", e);
+ throw new RuntimeException("Service died", e);
}
}
@Override
- public @NonNull Map<String, String> setParameters(@NonNull Map<String, String> parameters) {
+ public Map<String, String> setParameters(Map<String, String> parameters) {
try {
- return mTuner.setParameters(Objects.requireNonNull(parameters));
+ return mTuner.setParameters(Objects.requireNonNull(parameters,
+ "Parameters cannot be null"));
} catch (RemoteException e) {
- throw new RuntimeException("service died", e);
+ throw new RuntimeException("Service died", e);
}
}
@Override
- public @NonNull Map<String, String> getParameters(@NonNull List<String> keys) {
+ public Map<String, String> getParameters(List<String> keys) {
try {
- return mTuner.getParameters(Objects.requireNonNull(keys));
+ return mTuner.getParameters(Objects.requireNonNull(keys, "Keys cannot be null"));
} catch (RemoteException e) {
- throw new RuntimeException("service died", e);
+ throw new RuntimeException("Service died", e);
}
}
diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java
index e3f7001..b9782a8 100644
--- a/core/java/android/hardware/radio/TunerCallbackAdapter.java
+++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java
@@ -16,12 +16,13 @@
package android.hardware.radio;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -29,23 +30,31 @@
/**
* Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback.
*/
-class TunerCallbackAdapter extends ITunerCallback.Stub {
+final class TunerCallbackAdapter extends ITunerCallback.Stub {
private static final String TAG = "BroadcastRadio.TunerCallbackAdapter";
private final Object mLock = new Object();
- @NonNull private final RadioTuner.Callback mCallback;
- @NonNull private final Handler mHandler;
+ private final RadioTuner.Callback mCallback;
+ private final Handler mHandler;
+ @GuardedBy("mLock")
@Nullable ProgramList mProgramList;
// cache for deprecated methods
+ @GuardedBy("mLock")
boolean mIsAntennaConnected = true;
+
+ @GuardedBy("mLock")
@Nullable List<RadioManager.ProgramInfo> mLastCompleteList;
- private boolean mDelayedCompleteCallback = false;
+
+ @GuardedBy("mLock")
+ private boolean mDelayedCompleteCallback;
+
+ @GuardedBy("mLock")
@Nullable RadioManager.ProgramInfo mCurrentProgramInfo;
- TunerCallbackAdapter(@NonNull RadioTuner.Callback callback, @Nullable Handler handler) {
- mCallback = callback;
+ TunerCallbackAdapter(RadioTuner.Callback callback, @Nullable Handler handler) {
+ mCallback = Objects.requireNonNull(callback, "Callback cannot be null");
if (handler == null) {
mHandler = new Handler(Looper.getMainLooper());
} else {
@@ -55,31 +64,39 @@
void close() {
synchronized (mLock) {
- if (mProgramList != null) mProgramList.close();
+ if (mProgramList != null) {
+ mProgramList.close();
+ }
}
}
void setProgramListObserver(@Nullable ProgramList programList,
- @NonNull ProgramList.OnCloseListener closeListener) {
- Objects.requireNonNull(closeListener);
+ ProgramList.OnCloseListener closeListener) {
+ Objects.requireNonNull(closeListener, "CloseListener cannot be null");
synchronized (mLock) {
if (mProgramList != null) {
Log.w(TAG, "Previous program list observer wasn't properly closed, closing it...");
mProgramList.close();
}
mProgramList = programList;
- if (programList == null) return;
+ if (programList == null) {
+ return;
+ }
programList.setOnCloseListener(() -> {
synchronized (mLock) {
- if (mProgramList != programList) return;
+ if (mProgramList != programList) {
+ return;
+ }
mProgramList = null;
mLastCompleteList = null;
- closeListener.onClose();
}
+ closeListener.onClose();
});
programList.addOnCompleteListener(() -> {
synchronized (mLock) {
- if (mProgramList != programList) return;
+ if (mProgramList != programList) {
+ return;
+ }
mLastCompleteList = programList.toList();
if (mDelayedCompleteCallback) {
Log.d(TAG, "Sending delayed onBackgroundScanComplete callback");
@@ -109,7 +126,11 @@
}
boolean isAntennaConnected() {
- return mIsAntennaConnected;
+ boolean isConnected;
+ synchronized (mLock) {
+ isConnected = mIsAntennaConnected;
+ }
+ return isConnected;
}
@Override
@@ -177,7 +198,9 @@
@Override
public void onAntennaState(boolean connected) {
- mIsAntennaConnected = connected;
+ synchronized (mLock) {
+ mIsAntennaConnected = connected;
+ }
mHandler.post(() -> mCallback.onAntennaState(connected));
}
@@ -186,6 +209,7 @@
mHandler.post(() -> mCallback.onBackgroundScanAvailabilityChange(isAvailable));
}
+ @GuardedBy("mLock")
private void sendBackgroundScanCompleteLocked() {
mDelayedCompleteCallback = false;
mHandler.post(() -> mCallback.onBackgroundScanComplete());
@@ -213,8 +237,10 @@
public void onProgramListUpdated(ProgramList.Chunk chunk) {
mHandler.post(() -> {
synchronized (mLock) {
- if (mProgramList == null) return;
- mProgramList.apply(Objects.requireNonNull(chunk));
+ if (mProgramList == null) {
+ return;
+ }
+ mProgramList.apply(Objects.requireNonNull(chunk, "Chunk cannot be null"));
}
});
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 7436601..9ed55ff 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -967,7 +967,7 @@
Log.d(TAG, "Input should have started before starting Stylus handwriting.");
return;
}
- maybeCreateInkWindow();
+ maybeCreateAndInitInkWindow();
if (!mOnPreparedStylusHwCalled) {
// prepare hasn't been called by Stylus HOVER.
onPrepareStylusHandwriting();
@@ -1027,8 +1027,7 @@
*/
@Override
public void initInkWindow() {
- maybeCreateInkWindow();
- mInkWindow.initOnly();
+ maybeCreateAndInitInkWindow();
onPrepareStylusHandwriting();
mOnPreparedStylusHwCalled = true;
}
@@ -1036,11 +1035,13 @@
/**
* Create and attach token to Ink window if it wasn't already created.
*/
- private void maybeCreateInkWindow() {
+ private void maybeCreateAndInitInkWindow() {
if (mInkWindow == null) {
mInkWindow = new InkWindow(mWindow.getContext());
mInkWindow.setToken(mToken);
}
+ mInkWindow.initOnly();
+ setInkViewVisibilityListener();
// TODO(b/243571274): set an idle-timeout after which InkWindow is removed.
}
@@ -2469,13 +2470,19 @@
* @param motionEvent {@link MotionEvent} from stylus.
*/
public void onStylusHandwritingMotionEvent(@NonNull MotionEvent motionEvent) {
- if (mInkWindow.isInkViewVisible()) {
+ if (mInkWindow != null && mInkWindow.isInkViewVisible()) {
mInkWindow.getDecorView().dispatchTouchEvent(motionEvent);
} else {
if (mPendingEvents == null) {
mPendingEvents = new RingBuffer(MotionEvent.class, MAX_EVENTS_BUFFER);
}
mPendingEvents.append(motionEvent);
+ setInkViewVisibilityListener();
+ }
+ }
+
+ private void setInkViewVisibilityListener() {
+ if (mInkWindow != null) {
mInkWindow.setInkViewVisibilityListener(() -> {
if (mPendingEvents != null && !mPendingEvents.isEmpty()) {
for (MotionEvent event : mPendingEvents.toArray()) {
@@ -2539,6 +2546,7 @@
mHandler.removeCallbacks(mFinishHwRunnable);
}
mFinishHwRunnable = null;
+ mPendingEvents.clear();
final int requestId = mHandwritingRequestId.getAsInt();
mHandwritingRequestId = OptionalInt.empty();
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 88649cb..933769a 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -76,7 +76,7 @@
String getUserAccount(int userId);
void setUserAccount(int userId, String accountName);
long getUserCreationTime(int userId);
- boolean isUserSwitcherEnabled(int mUserId);
+ boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable, int mUserId);
boolean isRestricted(int userId);
boolean canHaveRestrictedProfile(int userId);
int getUserSerialNumber(int userId);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 1e91700..5c809a1 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5265,36 +5265,10 @@
public boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable) {
try {
- if (!mService.isUserSwitcherEnabled(mUserId)) {
- return false;
- }
+ return mService.isUserSwitcherEnabled(showEvenIfNotActionable, mUserId);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
-
- // The feature is enabled. But is it worth showing?
- return showEvenIfNotActionable
- || areThereUsersToWhichToSwitch() // There are switchable users.
- || !hasUserRestrictionForUser(DISALLOW_ADD_USER, mUserId); // New users can be added
- }
-
- /** Returns whether there are any users (other than the current user) to which to switch. */
- @RequiresPermission(anyOf = {
- android.Manifest.permission.MANAGE_USERS,
- android.Manifest.permission.CREATE_USERS
- })
- private boolean areThereUsersToWhichToSwitch() {
- final List<UserInfo> users = getAliveUsers();
- if (users == null) {
- return false;
- }
- int switchableUserCount = 0;
- for (UserInfo user : users) {
- if (user.supportsSwitchToByUser()) {
- ++switchableUserCount;
- }
- }
- return switchableUserCount > 1;
}
/**
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 08de87e..c1606e8 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2549,7 +2549,7 @@
* called on first creation of a new file on external storage, and whenever the
* media type of the file is updated later.
*
- * This API doesn't require any special permissions, though typical implementations
+ * This API requires MANAGE_EXTERNAL_STORAGE permission and typical implementations
* will require being called from an SELinux domain that allows setting file attributes
* related to quota (eg the GID or project ID).
*
@@ -2568,11 +2568,16 @@
* @hide
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE)
public void updateExternalStorageFileQuotaType(@NonNull File path,
@QuotaType int quotaType) throws IOException {
long projectId;
final String filePath = path.getCanonicalPath();
- final StorageVolume volume = getStorageVolume(path);
+ // MANAGE_EXTERNAL_STORAGE permission is required as FLAG_INCLUDE_SHARED_PROFILE is being
+ // set while querying getVolumeList.
+ final StorageVolume[] availableVolumes = getVolumeList(mContext.getUserId(),
+ FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE | FLAG_INCLUDE_SHARED_PROFILE);
+ final StorageVolume volume = getStorageVolume(availableVolumes, path);
if (volume == null) {
Log.w(TAG, "Failed to update quota type for " + filePath);
return;
diff --git a/core/java/android/preference/GenericInflater.java b/core/java/android/preference/GenericInflater.java
index 7edc987..fe53d4e 100644
--- a/core/java/android/preference/GenericInflater.java
+++ b/core/java/android/preference/GenericInflater.java
@@ -406,7 +406,7 @@
InflateException ie = new InflateException(attrs
.getPositionDescription()
+ ": Error inflating class "
- + constructor.getClass().getName());
+ + constructor.getDeclaringClass().getName());
ie.initCause(e);
throw ie;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3a9af7e..cab6acb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7462,6 +7462,13 @@
"trust_agents_initialized";
/**
+ * Set to 1 by the system after the list of known trust agents have been initialized.
+ * @hide
+ */
+ public static final String KNOWN_TRUST_AGENTS_INITIALIZED =
+ "known_trust_agents_initialized";
+
+ /**
* The Logging ID (a unique 64-bit value) as a hex string.
* Used as a pseudonymous identifier for logging.
* @deprecated This identifier is poorly initialized and has
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 154fcab..913efbd 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4820,5 +4820,12 @@
* @hide
*/
public static final String COLUMN_USAGE_SETTING = "usage_setting";
+
+ /**
+ * TelephonyProvider column name for user handle associated with this sim.
+ *
+ * @hide
+ */
+ public static final String COLUMN_USER_HANDLE = "user_handle";
}
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 067946e..b2a26fa 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -67,7 +67,6 @@
import android.view.displayhash.DisplayHash;
import android.view.displayhash.VerifiedDisplayHash;
import android.window.ITaskFpsCallback;
-import android.window.ScreenCapture;
/**
* System private interface to the window manager.
@@ -969,11 +968,4 @@
* treatment.
*/
boolean isLetterboxBackgroundMultiColored();
-
- /**
- * Captures the entire display specified by the displayId using the args provided. If the args
- * are null or if the sourceCrop is invalid or null, the entire display bounds will be captured.
- */
- oneway void captureDisplay(int displayId, in @nullable ScreenCapture.CaptureArgs captureArgs,
- in ScreenCapture.ScreenCaptureListener listener);
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index fb0cac3..2f7ae49 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -225,8 +225,6 @@
private static native void nativeSetDimmingEnabled(long transactionObj, long nativeObject,
boolean dimmingEnabled);
- private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
-
private static native void nativeSetInputWindowInfo(long transactionObj, long nativeObject,
InputWindowHandle handle);
@@ -449,6 +447,7 @@
private String mName;
/**
+ * Note: do not rename, this field is used by native code.
* @hide
*/
public long mNativeObject;
@@ -2037,18 +2036,6 @@
}
/**
- * Overrides HDR modes for a display device.
- *
- * If the caller does not have ACCESS_SURFACE_FLINGER permission, this will throw a Security
- * Exception.
- * @hide
- */
- @TestApi
- public static void overrideHdrTypes(@NonNull IBinder displayToken, @NonNull int[] modes) {
- nativeOverrideHdrTypes(displayToken, modes);
- }
-
- /**
* @hide
*/
public static long[] getPhysicalDisplayIds() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b6f775d..ec1f73f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -286,7 +286,7 @@
* @hide
*/
public static final boolean LOCAL_LAYOUT =
- SystemProperties.getBoolean("persist.debug.local_layout", false);
+ SystemProperties.getBoolean("persist.debug.local_layout", true);
/**
* Set this system property to true to force the view hierarchy to render
diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java
index b8f39a9..887d027 100644
--- a/core/java/android/window/ScreenCapture.java
+++ b/core/java/android/window/ScreenCapture.java
@@ -24,16 +24,11 @@
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.util.Log;
import android.view.SurfaceControl;
-import libcore.util.NativeAllocationRegistry;
-
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
/**
* Handles display and layer captures for the system.
@@ -44,23 +39,18 @@
private static final String TAG = "ScreenCapture";
private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs,
- long captureListener);
+ ScreenCaptureListener captureListener);
private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs,
- long captureListener);
- private static native long nativeCreateScreenCaptureListener(
- Consumer<ScreenshotHardwareBuffer> consumer);
- private static native void nativeWriteListenerToParcel(long nativeObject, Parcel out);
- private static native long nativeReadListenerFromParcel(Parcel in);
- private static native long getNativeListenerFinalizer();
+ ScreenCaptureListener captureListener);
/**
- * @param captureArgs Arguments about how to take the screenshot
+ * @param captureArgs Arguments about how to take the screenshot
* @param captureListener A listener to receive the screenshot callback
* @hide
*/
public static int captureDisplay(@NonNull DisplayCaptureArgs captureArgs,
@NonNull ScreenCaptureListener captureListener) {
- return nativeCaptureDisplay(captureArgs, captureListener.mNativeObject);
+ return nativeCaptureDisplay(captureArgs, captureListener);
}
/**
@@ -71,8 +61,10 @@
*/
public static ScreenshotHardwareBuffer captureDisplay(
DisplayCaptureArgs captureArgs) {
- SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener();
- int status = captureDisplay(captureArgs, screenCaptureListener.getScreenCaptureListener());
+ SyncScreenCaptureListener
+ screenCaptureListener = new SyncScreenCaptureListener();
+
+ int status = captureDisplay(captureArgs, screenCaptureListener);
if (status != 0) {
return null;
}
@@ -83,13 +75,14 @@
/**
* Captures a layer and its children and returns a {@link HardwareBuffer} with the content.
*
- * @param layer The root layer to capture.
- * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
- * Rect()' or null if no cropping is desired. If the root layer does not
- * have a buffer or a crop set, then a non-empty source crop must be
- * specified.
- * @param frameScale The desired scale of the returned buffer; the raw screen will be scaled
- * up/down.
+ * @param layer The root layer to capture.
+ * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
+ * Rect()' or null if no cropping is desired. If the root layer does not
+ * have a buffer or a crop set, then a non-empty source crop must be
+ * specified.
+ * @param frameScale The desired scale of the returned buffer; the raw
+ * screen will be scaled up/down.
+ *
* @return Returns a HardwareBuffer that contains the layer capture.
* @hide
*/
@@ -101,14 +94,15 @@
/**
* Captures a layer and its children and returns a {@link HardwareBuffer} with the content.
*
- * @param layer The root layer to capture.
- * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
- * Rect()' or null if no cropping is desired. If the root layer does not
- * have a buffer or a crop set, then a non-empty source crop must be
- * specified.
- * @param frameScale The desired scale of the returned buffer; the raw screen will be scaled
- * up/down.
- * @param format The desired pixel format of the returned buffer.
+ * @param layer The root layer to capture.
+ * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
+ * Rect()' or null if no cropping is desired. If the root layer does not
+ * have a buffer or a crop set, then a non-empty source crop must be
+ * specified.
+ * @param frameScale The desired scale of the returned buffer; the raw
+ * screen will be scaled up/down.
+ * @param format The desired pixel format of the returned buffer.
+ *
* @return Returns a HardwareBuffer that contains the layer capture.
* @hide
*/
@@ -130,7 +124,7 @@
LayerCaptureArgs captureArgs) {
SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener();
- int status = captureLayers(captureArgs, screenCaptureListener.getScreenCaptureListener());
+ int status = captureLayers(captureArgs, screenCaptureListener);
if (status != 0) {
return null;
}
@@ -141,7 +135,6 @@
/**
* Like {@link #captureLayers(SurfaceControl, Rect, float, int)} but with an array of layer
* handles to exclude.
- *
* @hide
*/
public static ScreenshotHardwareBuffer captureLayersExcluding(SurfaceControl layer,
@@ -157,13 +150,24 @@
}
/**
- * @param captureArgs Arguments about how to take the screenshot
+ * @param captureArgs Arguments about how to take the screenshot
* @param captureListener A listener to receive the screenshot callback
* @hide
*/
public static int captureLayers(@NonNull LayerCaptureArgs captureArgs,
@NonNull ScreenCaptureListener captureListener) {
- return nativeCaptureLayers(captureArgs, captureListener.mNativeObject);
+ return nativeCaptureLayers(captureArgs, captureListener);
+ }
+
+ /**
+ * @hide
+ */
+ public interface ScreenCaptureListener {
+ /**
+ * The callback invoked when the screen capture is complete.
+ * @param hardwareBuffer Data containing info about the screen capture.
+ */
+ void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer);
}
/**
@@ -186,16 +190,15 @@
mContainsHdrLayers = containsHdrLayers;
}
- /**
- * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object.
- *
- * @param hardwareBuffer The existing HardwareBuffer object
- * @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named}
- * @param containsSecureLayers Indicates whether this graphic buffer contains captured
- * contents of secure layers, in which case the screenshot
- * should not be persisted.
- * @param containsHdrLayers Indicates whether this graphic buffer contains HDR content.
- */
+ /**
+ * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object.
+ * @param hardwareBuffer The existing HardwareBuffer object
+ * @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named}
+ * @param containsSecureLayers Indicates whether this graphic buffer contains captured
+ * contents of secure layers, in which case the screenshot
+ * should not be persisted.
+ * @param containsHdrLayers Indicates whether this graphic buffer contains HDR content.
+ */
private static ScreenshotHardwareBuffer createFromNative(HardwareBuffer hardwareBuffer,
int namedColorSpace, boolean containsSecureLayers, boolean containsHdrLayers) {
ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.values()[namedColorSpace]);
@@ -217,7 +220,6 @@
public boolean containsSecureLayers() {
return mContainsSecureLayers;
}
-
/**
* Returns whether the screenshot contains at least one HDR layer.
* This information may be useful for informing the display whether this screenshot
@@ -232,7 +234,7 @@
* Note: If you want to modify the Bitmap in software, you will need to copy the Bitmap
* into
* a software Bitmap using {@link Bitmap#copy(Bitmap.Config, boolean)}
- * <p>
+ *
* CAVEAT: This can be extremely slow; avoid use unless absolutely necessary; prefer to
* directly
* use the {@link HardwareBuffer} directly.
@@ -248,23 +250,44 @@
}
}
+ private static class SyncScreenCaptureListener implements ScreenCaptureListener {
+ private static final int SCREENSHOT_WAIT_TIME_S = 1;
+ private ScreenshotHardwareBuffer mScreenshotHardwareBuffer;
+ private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+
+ @Override
+ public void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer) {
+ mScreenshotHardwareBuffer = hardwareBuffer;
+ mCountDownLatch.countDown();
+ }
+
+ private ScreenshotHardwareBuffer waitForScreenshot() {
+ try {
+ mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to wait for screen capture result", e);
+ }
+
+ return mScreenshotHardwareBuffer;
+ }
+ }
+
/**
* A common arguments class used for various screenshot requests. This contains arguments that
* are shared between {@link DisplayCaptureArgs} and {@link LayerCaptureArgs}
- *
* @hide
*/
- public static class CaptureArgs implements Parcelable {
- public final int mPixelFormat;
- public final Rect mSourceCrop = new Rect();
- public final float mFrameScaleX;
- public final float mFrameScaleY;
- public final boolean mCaptureSecureLayers;
- public final boolean mAllowProtected;
- public final long mUid;
- public final boolean mGrayscale;
+ private abstract static class CaptureArgs {
+ private final int mPixelFormat;
+ private final Rect mSourceCrop = new Rect();
+ private final float mFrameScaleX;
+ private final float mFrameScaleY;
+ private final boolean mCaptureSecureLayers;
+ private final boolean mAllowProtected;
+ private final long mUid;
+ private final boolean mGrayscale;
- private CaptureArgs(CaptureArgs.Builder<? extends CaptureArgs.Builder<?>> builder) {
+ private CaptureArgs(Builder<? extends Builder<?>> builder) {
mPixelFormat = builder.mPixelFormat;
mSourceCrop.set(builder.mSourceCrop);
mFrameScaleX = builder.mFrameScaleX;
@@ -275,23 +298,12 @@
mGrayscale = builder.mGrayscale;
}
- private CaptureArgs(Parcel in) {
- mPixelFormat = in.readInt();
- mSourceCrop.readFromParcel(in);
- mFrameScaleX = in.readFloat();
- mFrameScaleY = in.readFloat();
- mCaptureSecureLayers = in.readBoolean();
- mAllowProtected = in.readBoolean();
- mUid = in.readLong();
- mGrayscale = in.readBoolean();
- }
-
/**
* The Builder class used to construct {@link CaptureArgs}
*
- * @param <T> A builder that extends {@link CaptureArgs.Builder}
+ * @param <T> A builder that extends {@link Builder}
*/
- public static class Builder<T extends CaptureArgs.Builder<T>> {
+ abstract static class Builder<T extends Builder<T>> {
private int mPixelFormat = PixelFormat.RGBA_8888;
private final Rect mSourceCrop = new Rect();
private float mFrameScaleX = 1;
@@ -302,14 +314,6 @@
private boolean mGrayscale;
/**
- * Construct a new {@link CaptureArgs} with the set parameters. The builder remains
- * valid.
- */
- public CaptureArgs build() {
- return new CaptureArgs(this);
- }
-
- /**
* The desired pixel format of the returned buffer.
*/
public T setPixelFormat(int pixelFormat) {
@@ -391,47 +395,15 @@
/**
* Each sub class should return itself to allow the builder to chain properly
*/
- T getThis() {
- return (T) this;
- }
+ abstract T getThis();
}
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(mPixelFormat);
- mSourceCrop.writeToParcel(dest, flags);
- dest.writeFloat(mFrameScaleX);
- dest.writeFloat(mFrameScaleY);
- dest.writeBoolean(mCaptureSecureLayers);
- dest.writeBoolean(mAllowProtected);
- dest.writeLong(mUid);
- dest.writeBoolean(mGrayscale);
- }
-
- public static final Parcelable.Creator<CaptureArgs> CREATOR =
- new Parcelable.Creator<CaptureArgs>() {
- @Override
- public CaptureArgs createFromParcel(Parcel in) {
- return new CaptureArgs(in);
- }
-
- @Override
- public CaptureArgs[] newArray(int size) {
- return new CaptureArgs[size];
- }
- };
}
/**
* The arguments class used to make display capture requests.
*
+ * @see #nativeCaptureDisplay(DisplayCaptureArgs, ScreenCaptureListener)
* @hide
- * @see #nativeCaptureDisplay(DisplayCaptureArgs, long)
*/
public static class DisplayCaptureArgs extends CaptureArgs {
private final IBinder mDisplayToken;
@@ -516,8 +488,8 @@
/**
* The arguments class used to make layer capture requests.
*
+ * @see #nativeCaptureLayers(LayerCaptureArgs, ScreenCaptureListener)
* @hide
- * @see #nativeCaptureLayers(LayerCaptureArgs, long)
*/
public static class LayerCaptureArgs extends CaptureArgs {
private final long mNativeLayer;
@@ -558,17 +530,6 @@
return new LayerCaptureArgs(this);
}
- public Builder(SurfaceControl layer, CaptureArgs args) {
- setLayer(layer);
- setPixelFormat(args.mPixelFormat);
- setSourceCrop(args.mSourceCrop);
- setFrameScale(args.mFrameScaleX, args.mFrameScaleY);
- setCaptureSecureLayers(args.mCaptureSecureLayers);
- setAllowProtected(args.mAllowProtected);
- setUid(args.mUid);
- setGrayscale(args.mGrayscale);
- }
-
public Builder(SurfaceControl layer) {
setLayer(layer);
}
@@ -581,6 +542,7 @@
return this;
}
+
/**
* An array of layer handles to exclude.
*/
@@ -602,106 +564,8 @@
Builder getThis() {
return this;
}
+
}
}
- /**
- * The object used to receive the results when invoking screen capture requests via
- * {@link #captureDisplay(DisplayCaptureArgs, ScreenCaptureListener)} or
- * {@link #captureLayers(LayerCaptureArgs, ScreenCaptureListener)}
- */
- public static class ScreenCaptureListener implements Parcelable {
- private final long mNativeObject;
- private static final NativeAllocationRegistry sRegistry =
- NativeAllocationRegistry.createMalloced(
- ScreenCaptureListener.class.getClassLoader(), getNativeListenerFinalizer());
-
- /**
- * @param consumer The callback invoked when the screen capture is complete.
- */
- public ScreenCaptureListener(Consumer<ScreenshotHardwareBuffer> consumer) {
- mNativeObject = nativeCreateScreenCaptureListener(consumer);
- sRegistry.registerNativeAllocation(this, mNativeObject);
- }
-
- private ScreenCaptureListener(Parcel in) {
- if (in.readBoolean()) {
- mNativeObject = nativeReadListenerFromParcel(in);
- sRegistry.registerNativeAllocation(this, mNativeObject);
- } else {
- mNativeObject = 0;
- }
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- if (mNativeObject == 0) {
- dest.writeBoolean(false);
- } else {
- dest.writeBoolean(true);
- nativeWriteListenerToParcel(mNativeObject, dest);
- }
- }
-
- public static final Parcelable.Creator<ScreenCaptureListener> CREATOR =
- new Parcelable.Creator<ScreenCaptureListener>() {
- @Override
- public ScreenCaptureListener createFromParcel(Parcel in) {
- return new ScreenCaptureListener(in);
- }
-
- @Override
- public ScreenCaptureListener[] newArray(int size) {
- return new ScreenCaptureListener[0];
- }
- };
- }
-
- /**
- * A helper class to handle the async screencapture callbacks synchronously. This should only
- * be used if the screencapture caller doesn't care that it blocks waiting for a screenshot.
- */
- public static class SyncScreenCaptureListener {
- private static final int SCREENSHOT_WAIT_TIME_S = 1;
- private ScreenshotHardwareBuffer mScreenshotHardwareBuffer;
- private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
- private final ScreenCaptureListener mScreenCaptureListener;
-
- public SyncScreenCaptureListener() {
- mScreenCaptureListener = new ScreenCaptureListener(screenshotHardwareBuffer -> {
- mScreenshotHardwareBuffer = screenshotHardwareBuffer;
- mCountDownLatch.countDown();
- });
- }
-
- /**
- * @return The underlying {@link ScreenCaptureListener}
- */
- public ScreenCaptureListener getScreenCaptureListener() {
- return mScreenCaptureListener;
- }
-
- /**
- * Waits until the screenshot callback has been invoked and the screenshot is ready. This
- * can return {@code null} if the screenshot callback wasn't invoked after
- * {@link #SCREENSHOT_WAIT_TIME_S} or the screencapture request resulted in an error
- *
- * @return A ScreenshotHardwareBuffer for the content that was captured.
- */
- @Nullable
- public ScreenshotHardwareBuffer waitForScreenshot() {
- try {
- mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
- } catch (Exception e) {
- Log.e(TAG, "Failed to wait for screen capture result", e);
- }
-
- return mScreenshotHardwareBuffer;
- }
- }
}
diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java
index 56e9107..e2c8a31 100644
--- a/core/java/android/window/TaskFragmentInfo.java
+++ b/core/java/android/window/TaskFragmentInfo.java
@@ -188,6 +188,10 @@
/**
* Returns {@code true} if the parameters that are important for task fragment organizers are
* equal between this {@link TaskFragmentInfo} and {@param that}.
+ * Note that this method is usually called with
+ * {@link com.android.server.wm.WindowOrganizerController#configurationsAreEqualForOrganizer(
+ * Configuration, Configuration)} to determine if this {@link TaskFragmentInfo} should
+ * be dispatched to the client.
*/
public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentInfo that) {
if (that == null) {
diff --git a/core/java/android/window/ScreenCapture.aidl b/core/java/android/window/TaskFragmentParentInfo.aidl
similarity index 78%
rename from core/java/android/window/ScreenCapture.aidl
rename to core/java/android/window/TaskFragmentParentInfo.aidl
index 267a7c6..79d2209 100644
--- a/core/java/android/window/ScreenCapture.aidl
+++ b/core/java/android/window/TaskFragmentParentInfo.aidl
@@ -16,11 +16,8 @@
package android.window;
-/** @hide */
-parcelable ScreenCapture.CaptureArgs;
-
-/** @hide */
-parcelable ScreenCapture.ScreenshotHardwareBuffer;
-
-/** @hide */
-parcelable ScreenCapture.ScreenCaptureListener;
\ No newline at end of file
+/**
+ * The information about the parent Task of a particular TaskFragment
+ * @hide
+ */
+parcelable TaskFragmentParentInfo;
\ No newline at end of file
diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java
new file mode 100644
index 0000000..64b2638
--- /dev/null
+++ b/core/java/android/window/TaskFragmentParentInfo.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.WindowConfiguration;
+import android.content.res.Configuration;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The information about the parent Task of a particular TaskFragment
+ * @hide
+ */
+public class TaskFragmentParentInfo implements Parcelable {
+ @NonNull
+ private final Configuration mConfiguration = new Configuration();
+
+ private final int mDisplayId;
+
+ private final boolean mVisibleRequested;
+
+ public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId,
+ boolean visibleRequested) {
+ mConfiguration.setTo(configuration);
+ mDisplayId = displayId;
+ mVisibleRequested = visibleRequested;
+ }
+
+ public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
+ mConfiguration.setTo(info.getConfiguration());
+ mDisplayId = info.mDisplayId;
+ mVisibleRequested = info.mVisibleRequested;
+ }
+
+ /** The {@link Configuration} of the parent Task */
+ @NonNull
+ public Configuration getConfiguration() {
+ return mConfiguration;
+ }
+
+ /**
+ * The display ID of the parent Task. {@link android.view.Display#INVALID_DISPLAY} means the
+ * Task is detached from previously associated display.
+ */
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ /** Whether the parent Task is requested to be visible or not */
+ public boolean isVisibleRequested() {
+ return mVisibleRequested;
+ }
+
+ /**
+ * Returns {@code true} if the parameters which are important for task fragment
+ * organizers are equal between this {@link TaskFragmentParentInfo} and {@code that}.
+ * Note that this method is usually called with
+ * {@link com.android.server.wm.WindowOrganizerController#configurationsAreEqualForOrganizer(
+ * Configuration, Configuration)} to determine if this {@link TaskFragmentParentInfo} should
+ * be dispatched to the client.
+ */
+ public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentParentInfo that) {
+ if (that == null) {
+ return false;
+ }
+ return getWindowingMode() == that.getWindowingMode() && mDisplayId == that.mDisplayId
+ && mVisibleRequested == that.mVisibleRequested;
+ }
+
+ @WindowConfiguration.WindowingMode
+ private int getWindowingMode() {
+ return mConfiguration.windowConfiguration.getWindowingMode();
+ }
+
+ @Override
+ public String toString() {
+ return TaskFragmentParentInfo.class.getSimpleName() + ":{"
+ + "config=" + mConfiguration
+ + ", displayId=" + mDisplayId
+ + ", visibleRequested=" + mVisibleRequested
+ + "}";
+ }
+
+ /**
+ * Indicates that whether this {@link TaskFragmentParentInfo} equals to {@code obj}.
+ * Note that {@link #equalsForTaskFragmentOrganizer(TaskFragmentParentInfo)} should be used
+ * for most cases because not all {@link Configuration} properties are interested for
+ * {@link TaskFragmentOrganizer}.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof TaskFragmentParentInfo)) {
+ return false;
+ }
+ final TaskFragmentParentInfo that = (TaskFragmentParentInfo) obj;
+ return mConfiguration.equals(that.mConfiguration)
+ && mDisplayId == that.mDisplayId
+ && mVisibleRequested == that.mVisibleRequested;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mConfiguration.hashCode();
+ result = 31 * result + mDisplayId;
+ result = 31 * result + (mVisibleRequested ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mConfiguration.writeToParcel(dest, flags);
+ dest.writeInt(mDisplayId);
+ dest.writeBoolean(mVisibleRequested);
+ }
+
+ private TaskFragmentParentInfo(Parcel in) {
+ mConfiguration.readFromParcel(in);
+ mDisplayId = in.readInt();
+ mVisibleRequested = in.readBoolean();
+ }
+
+ public static final Creator<TaskFragmentParentInfo> CREATOR =
+ new Creator<TaskFragmentParentInfo>() {
+ @Override
+ public TaskFragmentParentInfo createFromParcel(Parcel in) {
+ return new TaskFragmentParentInfo(in);
+ }
+
+ @Override
+ public TaskFragmentParentInfo[] newArray(int size) {
+ return new TaskFragmentParentInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java
index 04fcd3a..7667743 100644
--- a/core/java/android/window/TaskFragmentTransaction.java
+++ b/core/java/android/window/TaskFragmentTransaction.java
@@ -173,10 +173,6 @@
/** @see #setTaskId(int) */
private int mTaskId;
- /** @see #setTaskConfiguration(Configuration) */
- @Nullable
- private Configuration mTaskConfiguration;
-
/** @see #setErrorCallbackToken(IBinder) */
@Nullable
private IBinder mErrorCallbackToken;
@@ -193,6 +189,9 @@
@Nullable
private IBinder mActivityToken;
+ @Nullable
+ private TaskFragmentParentInfo mTaskFragmentParentInfo;
+
public Change(@ChangeType int type) {
mType = type;
}
@@ -202,11 +201,11 @@
mTaskFragmentToken = in.readStrongBinder();
mTaskFragmentInfo = in.readTypedObject(TaskFragmentInfo.CREATOR);
mTaskId = in.readInt();
- mTaskConfiguration = in.readTypedObject(Configuration.CREATOR);
mErrorCallbackToken = in.readStrongBinder();
mErrorBundle = in.readBundle(TaskFragmentTransaction.class.getClassLoader());
mActivityIntent = in.readTypedObject(Intent.CREATOR);
mActivityToken = in.readStrongBinder();
+ mTaskFragmentParentInfo = in.readTypedObject(TaskFragmentParentInfo.CREATOR);
}
@Override
@@ -215,11 +214,11 @@
dest.writeStrongBinder(mTaskFragmentToken);
dest.writeTypedObject(mTaskFragmentInfo, flags);
dest.writeInt(mTaskId);
- dest.writeTypedObject(mTaskConfiguration, flags);
dest.writeStrongBinder(mErrorCallbackToken);
dest.writeBundle(mErrorBundle);
dest.writeTypedObject(mActivityIntent, flags);
dest.writeStrongBinder(mActivityToken);
+ dest.writeTypedObject(mTaskFragmentParentInfo, flags);
}
/** The change is related to the TaskFragment created with this unique token. */
@@ -243,10 +242,10 @@
return this;
}
+ // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release.
/** Configuration of the parent Task. */
@NonNull
public Change setTaskConfiguration(@NonNull Configuration configuration) {
- mTaskConfiguration = requireNonNull(configuration);
return this;
}
@@ -294,6 +293,19 @@
return this;
}
+ // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release.
+ /**
+ * Sets info of the parent Task of the embedded TaskFragment.
+ * @see TaskFragmentParentInfo
+ *
+ * @hide pending unhide
+ */
+ @NonNull
+ public Change setTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
+ mTaskFragmentParentInfo = requireNonNull(info);
+ return this;
+ }
+
@ChangeType
public int getType() {
return mType;
@@ -313,9 +325,10 @@
return mTaskId;
}
+ // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release.
@Nullable
public Configuration getTaskConfiguration() {
- return mTaskConfiguration;
+ return mTaskFragmentParentInfo.getConfiguration();
}
@Nullable
@@ -339,6 +352,13 @@
return mActivityToken;
}
+ // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release.
+ /** @hide pending unhide */
+ @Nullable
+ public TaskFragmentParentInfo getTaskFragmentParentInfo() {
+ return mTaskFragmentParentInfo;
+ }
+
@Override
public String toString() {
return "Change{ type=" + mType + " }";
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 33ea2e4..641d1a1 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -125,8 +125,15 @@
/** The container attaches work profile thumbnail for cross profile animation. */
public static final int FLAG_CROSS_PROFILE_WORK_THUMBNAIL = 1 << 13;
+ /**
+ * Whether the window is covered by an app starting window. This is different from
+ * {@link #FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT} which is only set on the Activity window
+ * that contains the starting window.
+ */
+ public static final int FLAG_IS_BEHIND_STARTING_WINDOW = 1 << 14;
+
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
- public static final int FLAG_FIRST_CUSTOM = 1 << 14;
+ public static final int FLAG_FIRST_CUSTOM = 1 << 15;
/** @hide */
@IntDef(prefix = { "FLAG_" }, value = {
@@ -145,6 +152,7 @@
FLAG_WILL_IME_SHOWN,
FLAG_CROSS_PROFILE_OWNER_THUMBNAIL,
FLAG_CROSS_PROFILE_WORK_THUMBNAIL,
+ FLAG_IS_BEHIND_STARTING_WINDOW,
FLAG_FIRST_CUSTOM
})
public @interface ChangeFlags {}
@@ -351,6 +359,9 @@
if ((flags & FLAG_FILLS_TASK) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("FILLS_TASK");
}
+ if ((flags & FLAG_IS_BEHIND_STARTING_WINDOW) != 0) {
+ sb.append(sb.length() == 0 ? "" : "|").append("IS_BEHIND_STARTING_WINDOW");
+ }
if ((flags & FLAG_FIRST_CUSTOM) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
}
diff --git a/core/java/com/android/internal/app/SimpleIconFactory.java b/core/java/com/android/internal/app/SimpleIconFactory.java
index 354eb62..be0b729 100644
--- a/core/java/com/android/internal/app/SimpleIconFactory.java
+++ b/core/java/com/android/internal/app/SimpleIconFactory.java
@@ -51,6 +51,7 @@
import android.util.TypedValue;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import org.xmlpull.v1.XmlPullParser;
@@ -69,6 +70,7 @@
private static final SynchronizedPool<SimpleIconFactory> sPool =
new SynchronizedPool<>(Runtime.getRuntime().availableProcessors());
+ private static boolean sPoolEnabled = true;
private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
private static final float BLUR_FACTOR = 1.5f / 48;
@@ -92,7 +94,7 @@
*/
@Deprecated
public static SimpleIconFactory obtain(Context ctx) {
- SimpleIconFactory instance = sPool.acquire();
+ SimpleIconFactory instance = sPoolEnabled ? sPool.acquire() : null;
if (instance == null) {
final ActivityManager am = (ActivityManager) ctx.getSystemService(ACTIVITY_SERVICE);
final int iconDpi = (am == null) ? 0 : am.getLauncherLargeIconDensity();
@@ -106,6 +108,17 @@
return instance;
}
+ /**
+ * Enables or disables SimpleIconFactory objects pooling. It is enabled in production, you
+ * could use this method in tests and disable the pooling to make the icon rendering more
+ * deterministic because some sizing parameters will not be cached. Please ensure that you
+ * reset this value back after finishing the test.
+ */
+ @VisibleForTesting
+ public static void setPoolEnabled(boolean poolEnabled) {
+ sPoolEnabled = poolEnabled;
+ }
+
private static int getAttrDimFromContext(Context ctx, @AttrRes int attrId, String errorMsg) {
final Resources res = ctx.getResources();
TypedValue outVal = new TypedValue();
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index b1e7d15..deafd19 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -1001,16 +1001,24 @@
}
/**
+ * This will enable jdwp by default for all apps. It is OK to cache this property
+ * because we expect to reboot the system whenever this property changes
+ */
+ private static final boolean ENABLE_JDWP = SystemProperties.get(
+ "persist.debug.dalvik.vm.jdwp.enabled").equals("1");
+
+ /**
* Applies debugger system properties to the zygote arguments.
*
- * If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
- * the debugger state is specified via the "--enable-jdwp" flag
- * in the spawn request.
+ * For eng builds all apps are debuggable. On userdebug and user builds
+ * if persist.debuggable.dalvik.vm.jdwp.enabled is 1 all apps are
+ * debuggable. Otherwise, the debugger state is specified via the
+ * "--enable-jdwp" flag in the spawn request.
*
* @param args non-null; zygote spawner args
*/
static void applyDebuggerSystemProperty(ZygoteArguments args) {
- if (RoSystemProperties.DEBUGGABLE) {
+ if (Build.IS_ENG || ENABLE_JDWP) {
args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
}
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 3e0a6cb..65c2d00 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -164,6 +164,7 @@
private static final String LOCK_SCREEN_DEVICE_OWNER_INFO = "lockscreen.device_owner_info";
private static final String ENABLED_TRUST_AGENTS = "lockscreen.enabledtrustagents";
+ private static final String KNOWN_TRUST_AGENTS = "lockscreen.knowntrustagents";
private static final String IS_TRUST_USUALLY_MANAGED = "lockscreen.istrustusuallymanaged";
public static final String CURRENT_LSKF_BASED_PROTECTOR_ID_KEY = "sp-handle";
@@ -1089,31 +1090,50 @@
return getString(LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, userId) != null;
}
+ /** Updates the list of enabled trust agent in LockSettings storage for the given user. */
public void setEnabledTrustAgents(Collection<ComponentName> activeTrustAgents, int userId) {
+ setString(ENABLED_TRUST_AGENTS, serializeTrustAgents(activeTrustAgents), userId);
+ getTrustManager().reportEnabledTrustAgentsChanged(userId);
+ }
+
+ /** Returns the list of enabled trust agent in LockSettings storage for the given user. */
+ public List<ComponentName> getEnabledTrustAgents(int userId) {
+ return deserializeTrustAgents(getString(ENABLED_TRUST_AGENTS, userId));
+ }
+
+ /** Updates the list of known trust agent in LockSettings storage for the given user. */
+ public void setKnownTrustAgents(Collection<ComponentName> knownTrustAgents, int userId) {
+ setString(KNOWN_TRUST_AGENTS, serializeTrustAgents(knownTrustAgents), userId);
+ }
+
+ /** Returns the list of known trust agent in LockSettings storage for the given user. */
+ public List<ComponentName> getKnownTrustAgents(int userId) {
+ return deserializeTrustAgents(getString(KNOWN_TRUST_AGENTS, userId));
+ }
+
+ private String serializeTrustAgents(Collection<ComponentName> trustAgents) {
StringBuilder sb = new StringBuilder();
- for (ComponentName cn : activeTrustAgents) {
+ for (ComponentName cn : trustAgents) {
if (sb.length() > 0) {
sb.append(',');
}
sb.append(cn.flattenToShortString());
}
- setString(ENABLED_TRUST_AGENTS, sb.toString(), userId);
- getTrustManager().reportEnabledTrustAgentsChanged(userId);
+ return sb.toString();
}
- public List<ComponentName> getEnabledTrustAgents(int userId) {
- String serialized = getString(ENABLED_TRUST_AGENTS, userId);
- if (TextUtils.isEmpty(serialized)) {
- return new ArrayList<ComponentName>();
+ private List<ComponentName> deserializeTrustAgents(String serializedTrustAgents) {
+ if (TextUtils.isEmpty(serializedTrustAgents)) {
+ return new ArrayList<>();
}
- String[] split = serialized.split(",");
- ArrayList<ComponentName> activeTrustAgents = new ArrayList<ComponentName>(split.length);
+ String[] split = serializedTrustAgents.split(",");
+ ArrayList<ComponentName> trustAgents = new ArrayList<>(split.length);
for (String s : split) {
if (!TextUtils.isEmpty(s)) {
- activeTrustAgents.add(ComponentName.unflattenFromString(s));
+ trustAgents.add(ComponentName.unflattenFromString(s));
}
}
- return activeTrustAgents;
+ return trustAgents;
}
/**
@@ -1487,7 +1507,8 @@
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
STRONG_AUTH_REQUIRED_AFTER_TIMEOUT,
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
- STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT})
+ STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT,
+ SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED})
@Retention(RetentionPolicy.SOURCE)
public @interface StrongAuthFlags {}
@@ -1540,6 +1561,12 @@
public static final int STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT = 0x80;
/**
+ * Some authentication is required because the trustagent either timed out or was disabled
+ * manually.
+ */
+ public static final int SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED = 0x100;
+
+ /**
* Strong auth flags that do not prevent biometric methods from being accepted as auth.
* If any other flags are set, biometric authentication is disabled.
*/
diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp
index f7a98d1..30d9ea1 100644
--- a/core/jni/android_os_MessageQueue.cpp
+++ b/core/jni/android_os_MessageQueue.cpp
@@ -51,6 +51,21 @@
virtual int handleEvent(int fd, int events, void* data);
+ /**
+ * A simple proxy that holds a weak reference to a looper callback.
+ */
+ class WeakLooperCallback : public LooperCallback {
+ protected:
+ virtual ~WeakLooperCallback();
+
+ public:
+ WeakLooperCallback(const wp<LooperCallback>& callback);
+ virtual int handleEvent(int fd, int events, void* data);
+
+ private:
+ wp<LooperCallback> mCallback;
+ };
+
private:
JNIEnv* mPollEnv;
jobject mPollObj;
@@ -131,7 +146,8 @@
if (events & CALLBACK_EVENT_OUTPUT) {
looperEvents |= Looper::EVENT_OUTPUT;
}
- mLooper->addFd(fd, Looper::POLL_CALLBACK, looperEvents, this,
+ mLooper->addFd(fd, Looper::POLL_CALLBACK, looperEvents,
+ sp<WeakLooperCallback>::make(this),
reinterpret_cast<void*>(events));
} else {
mLooper->removeFd(fd);
@@ -162,6 +178,24 @@
}
+// --- NativeMessageQueue::WeakLooperCallback ---
+
+NativeMessageQueue::WeakLooperCallback::WeakLooperCallback(const wp<LooperCallback>& callback) :
+ mCallback(callback) {
+}
+
+NativeMessageQueue::WeakLooperCallback::~WeakLooperCallback() {
+}
+
+int NativeMessageQueue::WeakLooperCallback::handleEvent(int fd, int events, void* data) {
+ sp<LooperCallback> callback = mCallback.promote();
+ if (callback != nullptr) {
+ return callback->handleEvent(fd, events, data);
+ }
+ return 0;
+}
+
+
// ----------------------------------------------------------------------------
sp<MessageQueue> android_os_MessageQueue_getMessageQueue(JNIEnv* env, jobject messageQueueObj) {
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 8c23b21..764338f 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -39,6 +39,7 @@
#include "androidfw/AssetManager2.h"
#include "androidfw/AttributeResolution.h"
#include "androidfw/MutexGuard.h"
+#include <androidfw/ResourceTimer.h>
#include "androidfw/ResourceTypes.h"
#include "androidfw/ResourceUtils.h"
@@ -630,6 +631,7 @@
jshort density, jobject typed_value,
jboolean resolve_references) {
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ ResourceTimer _tag(ResourceTimer::Counter::GetResourceValue);
auto value = assetmanager->GetResource(static_cast<uint32_t>(resid), false /*may_be_bag*/,
static_cast<uint16_t>(density));
if (!value.has_value()) {
@@ -1232,6 +1234,7 @@
}
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ ResourceTimer _tag(ResourceTimer::Counter::RetrieveAttributes);
ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr);
auto result =
RetrieveAttributes(assetmanager.get(), xml_parser, reinterpret_cast<uint32_t*>(attrs),
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index aefec6c..b11f22a 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -28,6 +28,7 @@
#include <android_runtime/android_graphics_GraphicBuffer.h>
#include <android_runtime/android_hardware_HardwareBuffer.h>
#include <android_runtime/android_view_Surface.h>
+#include <android_runtime/android_view_SurfaceControl.h>
#include <android_runtime/android_view_SurfaceSession.h>
#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
@@ -221,8 +222,14 @@
static struct {
jclass clazz;
+ jfieldID mNativeObject;
+} gTransactionClassInfo;
+
+static struct {
+ jclass clazz;
+ jfieldID mNativeObject;
jmethodID invokeReleaseCallback;
-} gInvokeReleaseCallback;
+} gSurfaceControlClassInfo;
constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode) {
switch (colorMode) {
@@ -511,10 +518,11 @@
if (fenceCopy) {
fenceCopy->incStrong(0);
}
- globalCallbackRef->env()->CallStaticVoidMethod(gInvokeReleaseCallback.clazz,
- gInvokeReleaseCallback.invokeReleaseCallback,
- globalCallbackRef->object(),
- reinterpret_cast<jlong>(fenceCopy));
+ globalCallbackRef->env()
+ ->CallStaticVoidMethod(gSurfaceControlClassInfo.clazz,
+ gSurfaceControlClassInfo.invokeReleaseCallback,
+ globalCallbackRef->object(),
+ reinterpret_cast<jlong>(fenceCopy));
};
}
@@ -1520,27 +1528,6 @@
transaction->reparent(ctrl, newParent);
}
-static void nativeOverrideHdrTypes(JNIEnv* env, jclass clazz, jobject tokenObject,
- jintArray jHdrTypes) {
- sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
- if (token == nullptr || jHdrTypes == nullptr) return;
-
- int* hdrTypes = env->GetIntArrayElements(jHdrTypes, 0);
- int numHdrTypes = env->GetArrayLength(jHdrTypes);
-
- std::vector<ui::Hdr> hdrTypesVector;
- for (int i = 0; i < numHdrTypes; i++) {
- hdrTypesVector.push_back(static_cast<ui::Hdr>(hdrTypes[i]));
- }
- env->ReleaseIntArrayElements(jHdrTypes, hdrTypes, 0);
-
- status_t error = SurfaceComposerClient::overrideHdrTypes(token, hdrTypesVector);
- if (error != NO_ERROR) {
- jniThrowExceptionFmt(env, "java/lang/SecurityException",
- "ACCESS_SURFACE_FLINGER is missing");
- }
-}
-
static jboolean nativeGetBootDisplayModeSupport(JNIEnv* env, jclass clazz) {
bool isBootDisplayModeSupported = false;
SurfaceComposerClient::getBootDisplayModeSupport(&isBootDisplayModeSupported);
@@ -1906,6 +1893,28 @@
// ----------------------------------------------------------------------------
+SurfaceControl* android_view_SurfaceControl_getNativeSurfaceControl(JNIEnv* env,
+ jobject surfaceControlObj) {
+ if (!!surfaceControlObj &&
+ env->IsInstanceOf(surfaceControlObj, gSurfaceControlClassInfo.clazz)) {
+ return reinterpret_cast<SurfaceControl*>(
+ env->GetLongField(surfaceControlObj, gSurfaceControlClassInfo.mNativeObject));
+ } else {
+ return nullptr;
+ }
+}
+
+SurfaceComposerClient::Transaction* android_view_SurfaceTransaction_getNativeSurfaceTransaction(
+ JNIEnv* env, jobject surfaceTransactionObj) {
+ if (!!surfaceTransactionObj &&
+ env->IsInstanceOf(surfaceTransactionObj, gTransactionClassInfo.clazz)) {
+ return reinterpret_cast<SurfaceComposerClient::Transaction*>(
+ env->GetLongField(surfaceTransactionObj, gTransactionClassInfo.mNativeObject));
+ } else {
+ return nullptr;
+ }
+}
+
static const JNINativeMethod sSurfaceControlMethods[] = {
// clang-format off
{"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J",
@@ -2026,8 +2035,6 @@
(void*)nativeSetGameContentType },
{"nativeGetCompositionDataspaces", "()[I",
(void*)nativeGetCompositionDataspaces},
- {"nativeOverrideHdrTypes", "(Landroid/os/IBinder;[I)V",
- (void*)nativeOverrideHdrTypes },
{"nativeClearContentFrameStats", "(J)Z",
(void*)nativeClearContentFrameStats },
{"nativeGetContentFrameStats", "(JLandroid/view/WindowContentFrameStats;)Z",
@@ -2306,11 +2313,18 @@
GetFieldIDOrDie(env, displayDecorationSupportClazz, "alphaInterpretation", "I");
jclass surfaceControlClazz = FindClassOrDie(env, "android/view/SurfaceControl");
- gInvokeReleaseCallback.clazz = MakeGlobalRefOrDie(env, surfaceControlClazz);
- gInvokeReleaseCallback.invokeReleaseCallback =
+ gSurfaceControlClassInfo.clazz = MakeGlobalRefOrDie(env, surfaceControlClazz);
+ gSurfaceControlClassInfo.mNativeObject =
+ GetFieldIDOrDie(env, gSurfaceControlClassInfo.clazz, "mNativeObject", "J");
+ gSurfaceControlClassInfo.invokeReleaseCallback =
GetStaticMethodIDOrDie(env, surfaceControlClazz, "invokeReleaseCallback",
"(Ljava/util/function/Consumer;J)V");
+ jclass surfaceTransactionClazz = FindClassOrDie(env, "android/view/SurfaceControl$Transaction");
+ gTransactionClassInfo.clazz = MakeGlobalRefOrDie(env, surfaceTransactionClazz);
+ gTransactionClassInfo.mNativeObject =
+ GetFieldIDOrDie(env, gTransactionClassInfo.clazz, "mNativeObject", "J");
+
return err;
}
diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp
index c1929c6..3bada15 100644
--- a/core/jni/android_window_ScreenCapture.cpp
+++ b/core/jni/android_window_ScreenCapture.cpp
@@ -61,8 +61,9 @@
} gLayerCaptureArgsClassInfo;
static struct {
- jmethodID accept;
-} gConsumerClassInfo;
+ jclass clazz;
+ jmethodID onScreenCaptureComplete;
+} gScreenCaptureListenerClassInfo;
static struct {
jclass clazz;
@@ -97,14 +98,14 @@
public:
explicit ScreenCaptureListenerWrapper(JNIEnv* env, jobject jobject) {
env->GetJavaVM(&mVm);
- mConsumerObject = env->NewGlobalRef(jobject);
- LOG_ALWAYS_FATAL_IF(!mConsumerObject, "Failed to make global ref");
+ mScreenCaptureListenerObject = env->NewGlobalRef(jobject);
+ LOG_ALWAYS_FATAL_IF(!mScreenCaptureListenerObject, "Failed to make global ref");
}
~ScreenCaptureListenerWrapper() {
- if (mConsumerObject) {
- getenv()->DeleteGlobalRef(mConsumerObject);
- mConsumerObject = nullptr;
+ if (mScreenCaptureListenerObject) {
+ getenv()->DeleteGlobalRef(mScreenCaptureListenerObject);
+ mScreenCaptureListenerObject = nullptr;
}
}
@@ -112,8 +113,9 @@
const gui::ScreenCaptureResults& captureResults) override {
JNIEnv* env = getenv();
if (!captureResults.fenceResult.ok() || captureResults.buffer == nullptr) {
- env->CallVoidMethod(mConsumerObject, gConsumerClassInfo.accept, nullptr);
- checkAndClearException(env, "accept");
+ env->CallVoidMethod(mScreenCaptureListenerObject,
+ gScreenCaptureListenerClassInfo.onScreenCaptureComplete, nullptr);
+ checkAndClearException(env, "onScreenCaptureComplete");
return binder::Status::ok();
}
captureResults.fenceResult.value()->waitForever(LOG_TAG);
@@ -128,15 +130,17 @@
captureResults.capturedSecureLayers,
captureResults.capturedHdrLayers);
checkAndClearException(env, "builder");
- env->CallVoidMethod(mConsumerObject, gConsumerClassInfo.accept, screenshotHardwareBuffer);
- checkAndClearException(env, "accept");
+ env->CallVoidMethod(mScreenCaptureListenerObject,
+ gScreenCaptureListenerClassInfo.onScreenCaptureComplete,
+ screenshotHardwareBuffer);
+ checkAndClearException(env, "onScreenCaptureComplete");
env->DeleteLocalRef(jhardwareBuffer);
env->DeleteLocalRef(screenshotHardwareBuffer);
return binder::Status::ok();
}
private:
- jobject mConsumerObject;
+ jobject mScreenCaptureListenerObject;
JavaVM* mVm;
JNIEnv* getenv() {
@@ -190,7 +194,7 @@
}
static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject,
- jlong screenCaptureListenerObject) {
+ jobject screenCaptureListenerObject) {
const DisplayCaptureArgs captureArgs =
displayCaptureArgsFromObject(env, displayCaptureArgsObject);
@@ -198,13 +202,13 @@
return BAD_VALUE;
}
- sp<gui::IScreenCaptureListener> captureListener =
- reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
+ sp<IScreenCaptureListener> captureListener =
+ sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject);
return ScreenshotClient::captureDisplay(captureArgs, captureListener);
}
static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject,
- jlong screenCaptureListenerObject) {
+ jobject screenCaptureListenerObject) {
LayerCaptureArgs captureArgs;
getCaptureArgs(env, layerCaptureArgsObject, captureArgs);
SurfaceControl* layer = reinterpret_cast<SurfaceControl*>(
@@ -234,70 +238,21 @@
}
}
- sp<gui::IScreenCaptureListener> captureListener =
- reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
+ sp<IScreenCaptureListener> captureListener =
+ sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject);
return ScreenshotClient::captureLayers(captureArgs, captureListener);
}
-static jlong nativeCreateScreenCaptureListener(JNIEnv* env, jclass clazz, jobject consumerObj) {
- sp<gui::IScreenCaptureListener> listener =
- sp<ScreenCaptureListenerWrapper>::make(env, consumerObj);
- listener->incStrong((void*)nativeCreateScreenCaptureListener);
- return reinterpret_cast<jlong>(listener.get());
-}
-
-static void nativeWriteListenerToParcel(JNIEnv* env, jclass clazz, jlong nativeObject,
- jobject parcelObj) {
- Parcel* parcel = parcelForJavaObject(env, parcelObj);
- if (parcel == NULL) {
- jniThrowNullPointerException(env, NULL);
- return;
- }
- ScreenCaptureListenerWrapper* const self =
- reinterpret_cast<ScreenCaptureListenerWrapper*>(nativeObject);
- if (self != nullptr) {
- parcel->writeStrongBinder(IInterface::asBinder(self));
- }
-}
-
-static jlong nativeReadListenerFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
- Parcel* parcel = parcelForJavaObject(env, parcelObj);
- if (parcel == NULL) {
- jniThrowNullPointerException(env, NULL);
- return 0;
- }
- sp<gui::IScreenCaptureListener> listener =
- interface_cast<gui::IScreenCaptureListener>(parcel->readStrongBinder());
- if (listener == nullptr) {
- return 0;
- }
- listener->incStrong((void*)nativeCreateScreenCaptureListener);
- return reinterpret_cast<jlong>(listener.get());
-}
-
-void destroyNativeListener(void* ptr) {
- ScreenCaptureListenerWrapper* listener = reinterpret_cast<ScreenCaptureListenerWrapper*>(ptr);
- listener->decStrong((void*)nativeCreateScreenCaptureListener);
-}
-
-static jlong getNativeListenerFinalizer(JNIEnv* env, jclass clazz) {
- return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeListener));
-}
-
// ----------------------------------------------------------------------------
static const JNINativeMethod sScreenCaptureMethods[] = {
// clang-format off
- {"nativeCaptureDisplay", "(Landroid/window/ScreenCapture$DisplayCaptureArgs;J)I",
+ {"nativeCaptureDisplay",
+ "(Landroid/window/ScreenCapture$DisplayCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I",
(void*)nativeCaptureDisplay },
- {"nativeCaptureLayers", "(Landroid/window/ScreenCapture$LayerCaptureArgs;J)I",
+ {"nativeCaptureLayers",
+ "(Landroid/window/ScreenCapture$LayerCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I",
(void*)nativeCaptureLayers },
- {"nativeCreateScreenCaptureListener", "(Ljava/util/function/Consumer;)J",
- (void*)nativeCreateScreenCaptureListener },
- {"nativeWriteListenerToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteListenerToParcel },
- {"nativeReadListenerFromParcel", "(Landroid/os/Parcel;)J",
- (void*)nativeReadListenerFromParcel },
- {"getNativeListenerFinalizer", "()J", (void*)getNativeListenerFinalizer },
// clang-format on
};
@@ -338,8 +293,12 @@
gLayerCaptureArgsClassInfo.childrenOnly =
GetFieldIDOrDie(env, layerCaptureArgsClazz, "mChildrenOnly", "Z");
- jclass consumer = FindClassOrDie(env, "java/util/function/Consumer");
- gConsumerClassInfo.accept = GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;)V");
+ jclass screenCaptureListenerClazz =
+ FindClassOrDie(env, "android/window/ScreenCapture$ScreenCaptureListener");
+ gScreenCaptureListenerClassInfo.clazz = MakeGlobalRefOrDie(env, screenCaptureListenerClazz);
+ gScreenCaptureListenerClassInfo.onScreenCaptureComplete =
+ GetMethodIDOrDie(env, screenCaptureListenerClazz, "onScreenCaptureComplete",
+ "(Landroid/window/ScreenCapture$ScreenshotHardwareBuffer;)V");
jclass screenshotGraphicsBufferClazz =
FindClassOrDie(env, "android/window/ScreenCapture$ScreenshotHardwareBuffer");
diff --git a/core/jni/include/android_runtime/android_view_SurfaceControl.h b/core/jni/include/android_runtime/android_view_SurfaceControl.h
new file mode 100644
index 0000000..10a7549
--- /dev/null
+++ b/core/jni/include/android_runtime/android_view_SurfaceControl.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_VIEW_SURFACECONTROL_H
+#define _ANDROID_VIEW_SURFACECONTROL_H
+
+#include <gui/SurfaceComposerClient.h>
+#include <gui/SurfaceControl.h>
+
+#include "jni.h"
+
+namespace android {
+
+/* Gets the underlying native SurfaceControl for a java SurfaceControl. */
+extern SurfaceControl* android_view_SurfaceControl_getNativeSurfaceControl(
+ JNIEnv* env, jobject surfaceControlObj);
+
+/* Gets the underlying native SurfaceControl for a java SurfaceControl. */
+extern SurfaceComposerClient::Transaction*
+android_view_SurfaceTransaction_getNativeSurfaceTransaction(JNIEnv* env,
+ jobject surfaceTransactionObj);
+
+} // namespace android
+
+#endif // _ANDROID_VIEW_SURFACECONTROL_H
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1d2ce7e..060f440 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1662,13 +1662,18 @@
darkening hysteresis constraint value is the n-th element of
config_screenDarkeningThresholds.
+ Historically, it has been assumed that this will be an integer array with values in the
+ range of [0, 255]. However, it is now assumed to be a float array with values in the
+ range of [0, 1]. To accommodate both the possibilities, we internally check the scale on
+ which the thresholds are defined, and calibrate it accordingly.
+
The (zero-based) index is calculated as follows: (MAX is the largest index of the array)
condition calculated index
value < level[0] 0
level[n] <= value < level[n+1] n+1
level[MAX] <= value MAX+1 -->
- <integer-array name="config_screenThresholdLevels">
- </integer-array>
+ <array name="config_screenThresholdLevels">
+ </array>
<!-- Array of hysteresis constraint values for brightening, represented as tenths of a
percent. The length of this array is assumed to be one greater than
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index 4c247427..9e39e13 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -16,11 +16,12 @@
package android.hardware.devicestate;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -36,7 +37,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
import java.util.HashSet;
import java.util.Set;
@@ -59,6 +59,7 @@
public void setUp() {
mService = new TestDeviceStateManagerService();
mDeviceStateManagerGlobal = new DeviceStateManagerGlobal(mService);
+ assertFalse(mService.mCallbacks.isEmpty());
}
@Test
@@ -79,8 +80,8 @@
verify(callback2).onBaseStateChanged(eq(mService.getBaseState()));
verify(callback2).onStateChanged(eq(mService.getMergedState()));
- Mockito.reset(callback1);
- Mockito.reset(callback2);
+ reset(callback1);
+ reset(callback2);
// Change the supported states and verify callback
mService.setSupportedStates(new int[]{ DEFAULT_DEVICE_STATE });
@@ -88,8 +89,8 @@
verify(callback2).onSupportedStatesChanged(eq(mService.getSupportedStates()));
mService.setSupportedStates(new int[]{ DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE });
- Mockito.reset(callback1);
- Mockito.reset(callback2);
+ reset(callback1);
+ reset(callback2);
// Change the base state and verify callback
mService.setBaseState(OTHER_DEVICE_STATE);
@@ -98,8 +99,8 @@
verify(callback2).onBaseStateChanged(eq(mService.getBaseState()));
verify(callback2).onStateChanged(eq(mService.getMergedState()));
- Mockito.reset(callback1);
- Mockito.reset(callback2);
+ reset(callback1);
+ reset(callback2);
// Change the requested state and verify callback
DeviceStateRequest request = DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE).build();
@@ -120,7 +121,7 @@
verify(callback).onSupportedStatesChanged(eq(mService.getSupportedStates()));
verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
verify(callback).onStateChanged(eq(mService.getMergedState()));
- Mockito.reset(callback);
+ reset(callback);
mDeviceStateManagerGlobal.unregisterDeviceStateCallback(callback);
@@ -130,29 +131,19 @@
}
@Test
- public void submittingRequestRegistersCallback() {
- assertTrue(mService.mCallbacks.isEmpty());
-
- DeviceStateRequest request = DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE).build();
- mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */);
-
- assertFalse(mService.mCallbacks.isEmpty());
- }
-
- @Test
public void submitRequest() {
DeviceStateCallback callback = mock(DeviceStateCallback.class);
mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
ConcurrentUtils.DIRECT_EXECUTOR);
verify(callback).onStateChanged(eq(mService.getBaseState()));
- Mockito.reset(callback);
+ reset(callback);
DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */);
verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
- Mockito.reset(callback);
+ reset(callback);
mDeviceStateManagerGlobal.cancelStateRequest();
@@ -160,6 +151,69 @@
}
@Test
+ public void submitBaseStateOverrideRequest() {
+ DeviceStateCallback callback = mock(DeviceStateCallback.class);
+ mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
+ ConcurrentUtils.DIRECT_EXECUTOR);
+
+ verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
+ verify(callback).onStateChanged(eq(mService.getBaseState()));
+ reset(callback);
+
+ DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
+ mDeviceStateManagerGlobal.requestBaseStateOverride(request, null /* executor */,
+ null /* callback */);
+
+ verify(callback).onBaseStateChanged(eq(OTHER_DEVICE_STATE));
+ verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
+ reset(callback);
+
+ mDeviceStateManagerGlobal.cancelBaseStateOverride();
+
+ verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
+ verify(callback).onStateChanged(eq(mService.getBaseState()));
+ }
+
+ @Test
+ public void submitBaseAndEmulatedStateOverride() {
+ DeviceStateCallback callback = mock(DeviceStateCallback.class);
+ mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
+ ConcurrentUtils.DIRECT_EXECUTOR);
+
+ verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
+ verify(callback).onStateChanged(eq(mService.getBaseState()));
+ reset(callback);
+
+ DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
+ mDeviceStateManagerGlobal.requestBaseStateOverride(request, null /* executor */,
+ null /* callback */);
+
+ verify(callback).onBaseStateChanged(eq(OTHER_DEVICE_STATE));
+ verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
+ assertEquals(OTHER_DEVICE_STATE, mService.getBaseState());
+ reset(callback);
+
+ DeviceStateRequest secondRequest = DeviceStateRequest.newBuilder(
+ DEFAULT_DEVICE_STATE).build();
+
+ mDeviceStateManagerGlobal.requestState(secondRequest, null, null);
+
+ assertEquals(OTHER_DEVICE_STATE, mService.getBaseState());
+ verify(callback).onStateChanged(eq(DEFAULT_DEVICE_STATE));
+ reset(callback);
+
+ mDeviceStateManagerGlobal.cancelStateRequest();
+
+ verify(callback).onStateChanged(OTHER_DEVICE_STATE);
+ reset(callback);
+
+ mDeviceStateManagerGlobal.cancelBaseStateOverride();
+
+ verify(callback).onBaseStateChanged(DEFAULT_DEVICE_STATE);
+ verify(callback).onStateChanged(DEFAULT_DEVICE_STATE);
+ }
+
+ @Test
public void verifyDeviceStateRequestCallbacksCalled() {
DeviceStateRequest.Callback callback = mock(TestDeviceStateRequestCallback.class);
@@ -169,7 +223,7 @@
callback /* callback */);
verify(callback).onRequestActivated(eq(request));
- Mockito.reset(callback);
+ reset(callback);
mDeviceStateManagerGlobal.cancelStateRequest();
@@ -203,13 +257,16 @@
private int[] mSupportedStates = new int[] { DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE };
private int mBaseState = DEFAULT_DEVICE_STATE;
private Request mRequest;
+ private Request mBaseStateRequest;
private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>();
private DeviceStateInfo getInfo() {
+ final int mergedBaseState = mBaseStateRequest == null
+ ? mBaseState : mBaseStateRequest.state;
final int mergedState = mRequest == null
- ? mBaseState : mRequest.state;
- return new DeviceStateInfo(mSupportedStates, mBaseState, mergedState);
+ ? mergedBaseState : mRequest.state;
+ return new DeviceStateInfo(mSupportedStates, mergedBaseState, mergedState);
}
private void notifyDeviceStateInfoChanged() {
@@ -238,7 +295,7 @@
try {
callback.onDeviceStateInfoChanged(getInfo());
} catch (RemoteException e) {
- // Do nothing. Should never happen.
+ e.rethrowFromSystemServer();
}
}
@@ -249,7 +306,7 @@
try {
callback.onRequestCanceled(mRequest.token);
} catch (RemoteException e) {
- // Do nothing. Should never happen.
+ e.rethrowFromSystemServer();
}
}
}
@@ -262,7 +319,7 @@
try {
callback.onRequestActive(token);
} catch (RemoteException e) {
- // Do nothing. Should never happen.
+ e.rethrowFromSystemServer();
}
}
}
@@ -275,7 +332,46 @@
try {
callback.onRequestCanceled(token);
} catch (RemoteException e) {
- // Do nothing. Should never happen.
+ e.rethrowFromSystemServer();
+ }
+ }
+ notifyDeviceStateInfoChanged();
+ }
+
+ @Override
+ public void requestBaseStateOverride(IBinder token, int state, int flags) {
+ if (mBaseStateRequest != null) {
+ for (IDeviceStateManagerCallback callback : mCallbacks) {
+ try {
+ callback.onRequestCanceled(mBaseStateRequest.token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ final Request request = new Request(token, state, flags);
+ mBaseStateRequest = request;
+ notifyDeviceStateInfoChanged();
+
+ for (IDeviceStateManagerCallback callback : mCallbacks) {
+ try {
+ callback.onRequestActive(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ @Override
+ public void cancelBaseStateOverride() throws RemoteException {
+ IBinder token = mBaseStateRequest.token;
+ mBaseStateRequest = null;
+ for (IDeviceStateManagerCallback callback : mCallbacks) {
+ try {
+ callback.onRequestCanceled(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
}
}
notifyDeviceStateInfoChanged();
@@ -296,7 +392,7 @@
}
public int getBaseState() {
- return mBaseState;
+ return getInfo().baseState;
}
public int getMergedState() {
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
index 940ca96..4679a9e 100644
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
@@ -19,14 +19,15 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -51,8 +52,11 @@
import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
import com.android.internal.widget.LockPatternUtils;
+import com.google.android.collect.Lists;
+
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import java.nio.charset.StandardCharsets;
@@ -172,13 +176,61 @@
}
@Test
- public void testGetEnabledTrustAgentsNotNull() throws RemoteException {
+ public void testSetEnabledTrustAgents() throws RemoteException {
int testUserId = 10;
ILockSettings ils = createTestLockSettings();
- when(ils.getString(anyString(), any(), anyInt())).thenReturn("");
+ ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
+ doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
+ List<ComponentName> enabledTrustAgents = Lists.newArrayList(
+ ComponentName.unflattenFromString("com.android/.TrustAgent"),
+ ComponentName.unflattenFromString("com.test/.TestAgent"));
+
+ mLockPatternUtils.setEnabledTrustAgents(enabledTrustAgents, testUserId);
+
+ assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
+ }
+
+ @Test
+ public void testGetEnabledTrustAgents() throws RemoteException {
+ int testUserId = 10;
+ ILockSettings ils = createTestLockSettings();
+ when(ils.getString(anyString(), any(), anyInt())).thenReturn(
+ "com.android/.TrustAgent,com.test/.TestAgent");
+
List<ComponentName> trustAgents = mLockPatternUtils.getEnabledTrustAgents(testUserId);
- assertNotNull(trustAgents);
- assertEquals(0, trustAgents.size());
+
+ assertThat(trustAgents).containsExactly(
+ ComponentName.unflattenFromString("com.android/.TrustAgent"),
+ ComponentName.unflattenFromString("com.test/.TestAgent"));
+ }
+
+ @Test
+ public void testSetKnownTrustAgents() throws RemoteException {
+ int testUserId = 10;
+ ILockSettings ils = createTestLockSettings();
+ ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
+ doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
+ List<ComponentName> knownTrustAgents = Lists.newArrayList(
+ ComponentName.unflattenFromString("com.android/.TrustAgent"),
+ ComponentName.unflattenFromString("com.test/.TestAgent"));
+
+ mLockPatternUtils.setKnownTrustAgents(knownTrustAgents, testUserId);
+
+ assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
+ }
+
+ @Test
+ public void testGetKnownTrustAgents() throws RemoteException {
+ int testUserId = 10;
+ ILockSettings ils = createTestLockSettings();
+ when(ils.getString(anyString(), any(), anyInt())).thenReturn(
+ "com.android/.TrustAgent,com.test/.TestAgent");
+
+ List<ComponentName> trustAgents = mLockPatternUtils.getKnownTrustAgents(testUserId);
+
+ assertThat(trustAgents).containsExactly(
+ ComponentName.unflattenFromString("com.android/.TrustAgent"),
+ ComponentName.unflattenFromString("com.test/.TestAgent"));
}
private ILockSettings createTestLockSettings() {
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 682483d9..4f9af12 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -373,12 +373,6 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/TransitionController.java"
},
- "-1750384749": {
- "message": "Launch on display check: allow launch on public display",
- "level": "DEBUG",
- "group": "WM_DEBUG_TASKS",
- "at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
- },
"-1750206390": {
"message": "Exception thrown when creating surface for client %s (%s). %s",
"level": "WARN",
@@ -907,6 +901,12 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1253056469": {
+ "message": "Launch on display check: %s launch for userId=%d on displayId=%d",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
+ },
"-1248645819": {
"message": "\tAdd container=%s",
"level": "DEBUG",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 7e9c418..fb0a9db 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -41,7 +41,7 @@
// TODO(b/241126279) Introduce constants to better version functionality
@Override
public int getVendorApiLevel() {
- return 2;
+ return 1;
}
/**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index febd791..74303e2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -31,6 +31,7 @@
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
+import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -93,6 +94,7 @@
}
/** No longer overrides the animation if the transition is on the given Task. */
+ @GuardedBy("mLock")
void stopOverrideSplitAnimation(int taskId) {
if (mAnimationController != null) {
mAnimationController.unregisterRemoteAnimations(taskId);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index c8ac0fc..00be5a6e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -17,8 +17,10 @@
package androidx.window.extensions.embedding;
import android.app.Activity;
+import android.content.res.Configuration;
import android.util.Pair;
import android.util.Size;
+import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
@@ -32,14 +34,18 @@
private final TaskFragmentContainer mSecondaryContainer;
@NonNull
private final SplitRule mSplitRule;
+ @NonNull
+ private SplitAttributes mSplitAttributes;
SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
@NonNull Activity primaryActivity,
@NonNull TaskFragmentContainer secondaryContainer,
- @NonNull SplitRule splitRule) {
+ @NonNull SplitRule splitRule,
+ @NonNull SplitAttributes splitAttributes) {
mPrimaryContainer = primaryContainer;
mSecondaryContainer = secondaryContainer;
mSplitRule = splitRule;
+ mSplitAttributes = splitAttributes;
if (shouldFinishPrimaryWithSecondary(splitRule)) {
if (mPrimaryContainer.getRunningActivityCount() == 1
@@ -72,6 +78,26 @@
return mSplitRule;
}
+ @NonNull
+ SplitAttributes getSplitAttributes() {
+ return mSplitAttributes;
+ }
+
+ /**
+ * Updates the {@link SplitAttributes} to this container.
+ * It is usually used when there's a folding state change or
+ * {@link SplitController#onTaskFragmentParentInfoChanged(WindowContainerTransaction, int,
+ * Configuration)}.
+ */
+ void setSplitAttributes(@NonNull SplitAttributes splitAttributes) {
+ mSplitAttributes = splitAttributes;
+ }
+
+ @NonNull
+ TaskContainer getTaskContainer() {
+ return getPrimaryContainer().getTaskContainer();
+ }
+
/** Returns the minimum dimension pair of primary container and secondary container. */
@NonNull
Pair<Size, Size> getMinDimensionsPair() {
@@ -141,6 +167,7 @@
+ " primaryContainer=" + mPrimaryContainer
+ " secondaryContainer=" + mSecondaryContainer
+ " splitRule=" + mSplitRule
+ + " splitAttributes" + mSplitAttributes
+ "}";
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 126f835..203ece0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -19,6 +19,7 @@
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
@@ -40,12 +41,13 @@
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
import static androidx.window.extensions.embedding.SplitPresenter.getNonEmbeddedActivityBounds;
-import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide;
+import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
import android.app.Activity;
import android.app.ActivityClient;
import android.app.ActivityOptions;
import android.app.ActivityThread;
+import android.app.Application;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
@@ -62,19 +64,25 @@
import android.util.Pair;
import android.util.Size;
import android.util.SparseArray;
+import android.view.WindowMetrics;
import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentParentInfo;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
+import androidx.window.extensions.WindowExtensionsProvider;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -96,6 +104,23 @@
@GuardedBy("mLock")
private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
/**
+ * A developer-defined {@link SplitAttributes} calculator to compute the current
+ * {@link SplitAttributes} with the current device and window states.
+ * It is registered via {@link #setSplitAttributesCalculator(SplitAttributesCalculator)}
+ * and unregistered via {@link #clearSplitAttributesCalculator()}.
+ * This is called when:
+ * <ul>
+ * <li>{@link SplitPresenter#updateSplitContainer(SplitContainer, TaskFragmentContainer,
+ * WindowContainerTransaction)}</li>
+ * <li>There's a started Activity which matches {@link SplitPairRule} </li>
+ * <li>Checking whether the place holder should be launched if there's a Activity matches
+ * {@link SplitPlaceholderRule} </li>
+ * </ul>
+ */
+ @GuardedBy("mLock")
+ @Nullable
+ private SplitAttributesCalculator mSplitAttributesCalculator;
+ /**
* Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info
* below it.
* When the app is host of multiple Tasks, there can be multiple splits controlled by the same
@@ -105,26 +130,65 @@
@GuardedBy("mLock")
final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>();
- // Callback to Jetpack to notify about changes to split states.
- @NonNull
+ /** Callback to Jetpack to notify about changes to split states. */
+ @Nullable
private Consumer<List<SplitInfo>> mEmbeddingCallback;
private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
private final Handler mHandler;
final Object mLock = new Object();
private final ActivityStartMonitor mActivityStartMonitor;
+ @NonNull
+ final WindowLayoutComponentImpl mWindowLayoutComponent;
public SplitController() {
+ this((WindowLayoutComponentImpl) Objects.requireNonNull(WindowExtensionsProvider
+ .getWindowExtensions().getWindowLayoutComponent()));
+ }
+
+ @VisibleForTesting
+ SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent) {
final MainThreadExecutor executor = new MainThreadExecutor();
mHandler = executor.mHandler;
mPresenter = new SplitPresenter(executor, this);
- ActivityThread activityThread = ActivityThread.currentActivityThread();
+ final ActivityThread activityThread = ActivityThread.currentActivityThread();
+ final Application application = activityThread.getApplication();
// Register a callback to be notified about activities being created.
- activityThread.getApplication().registerActivityLifecycleCallbacks(
- new LifecycleCallbacks());
+ application.registerActivityLifecycleCallbacks(new LifecycleCallbacks());
// Intercept activity starts to route activities to new containers if necessary.
Instrumentation instrumentation = activityThread.getInstrumentation();
+
mActivityStartMonitor = new ActivityStartMonitor();
instrumentation.addMonitor(mActivityStartMonitor);
+ mWindowLayoutComponent = windowLayoutComponent;
+ mWindowLayoutComponent.addFoldingStateChangedCallback(new FoldingFeatureListener());
+ }
+
+ private class FoldingFeatureListener implements Consumer<List<CommonFoldingFeature>> {
+ @Override
+ public void accept(List<CommonFoldingFeature> foldingFeatures) {
+ synchronized (mLock) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ for (int i = 0; i < mTaskContainers.size(); i++) {
+ final TaskContainer taskContainer = mTaskContainers.valueAt(i);
+ if (!taskContainer.isVisible()) {
+ continue;
+ }
+ if (taskContainer.getDisplayId() != DEFAULT_DISPLAY) {
+ continue;
+ }
+ // TODO(b/238948678): Support reporting display features in all windowing modes.
+ if (taskContainer.isInMultiWindow()) {
+ continue;
+ }
+ if (taskContainer.isEmpty()) {
+ continue;
+ }
+ updateContainersInTask(wct, taskContainer);
+ updateAnimationOverride(taskContainer);
+ }
+ mPresenter.applyTransaction(wct);
+ }
+ }
}
/** Updates the embedding rules applied to future activity launches. */
@@ -141,12 +205,22 @@
@Override
public void setSplitAttributesCalculator(@NonNull SplitAttributesCalculator calculator) {
- // TODO: Implement this method
+ synchronized (mLock) {
+ mSplitAttributesCalculator = calculator;
+ }
}
@Override
public void clearSplitAttributesCalculator() {
- // TODO: Implement this method
+ synchronized (mLock) {
+ mSplitAttributesCalculator = null;
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ SplitAttributesCalculator getSplitAttributesCalculator() {
+ return mSplitAttributesCalculator;
}
@NonNull
@@ -191,7 +265,8 @@
onTaskFragmentVanished(wct, info);
break;
case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED:
- onTaskFragmentParentInfoChanged(wct, taskId, change.getTaskConfiguration());
+ onTaskFragmentParentInfoChanged(wct, taskId,
+ change.getTaskFragmentParentInfo());
break;
case TYPE_TASK_FRAGMENT_ERROR:
final Bundle errorBundle = change.getErrorBundle();
@@ -346,22 +421,33 @@
*
* @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
* @param taskId Id of the parent Task that is changed.
- * @param parentConfig Config of the parent Task.
+ * @param parentInfo {@link TaskFragmentParentInfo} of the parent Task.
*/
@VisibleForTesting
@GuardedBy("mLock")
void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
- int taskId, @NonNull Configuration parentConfig) {
- onTaskConfigurationChanged(taskId, parentConfig);
- if (isInPictureInPicture(parentConfig)) {
- // No need to update presentation in PIP until the Task exit PIP.
- return;
- }
+ int taskId, @NonNull TaskFragmentParentInfo parentInfo) {
final TaskContainer taskContainer = getTaskContainer(taskId);
if (taskContainer == null || taskContainer.isEmpty()) {
Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId);
return;
}
+ taskContainer.updateTaskFragmentParentInfo(parentInfo);
+ if (!taskContainer.isVisible()) {
+ // Don't update containers if the task is not visible. We only update containers when
+ // parentInfo#isVisibleRequested is true.
+ return;
+ }
+ onTaskContainerInfoChanged(taskContainer, parentInfo.getConfiguration());
+ if (isInPictureInPicture(parentInfo.getConfiguration())) {
+ // No need to update presentation in PIP until the Task exit PIP.
+ return;
+ }
+ updateContainersInTask(wct, taskContainer);
+ }
+
+ private void updateContainersInTask(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskContainer taskContainer) {
// Update all TaskFragments in the Task. Make a copy of the list since some may be
// removed on updating.
final List<TaskFragmentContainer> containers =
@@ -486,6 +572,7 @@
}
/** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */
+ @GuardedBy("mLock")
private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
final TaskContainer taskContainer = mTaskContainers.valueAt(i);
@@ -501,14 +588,11 @@
}
}
- private void onTaskConfigurationChanged(int taskId, @NonNull Configuration config) {
- final TaskContainer taskContainer = mTaskContainers.get(taskId);
- if (taskContainer == null) {
- return;
- }
+ @GuardedBy("mLock")
+ private void onTaskContainerInfoChanged(@NonNull TaskContainer taskContainer,
+ @NonNull Configuration config) {
final boolean wasInPip = taskContainer.isInPictureInPicture();
final boolean isInPIp = isInPictureInPicture(config);
- taskContainer.setWindowingMode(config.windowConfiguration.getWindowingMode());
// We need to check the animation override when enter/exit PIP or has bounds changed.
boolean shouldUpdateAnimationOverride = wasInPip != isInPIp;
@@ -526,37 +610,49 @@
* Updates if we should override transition animation. We only want to override if the Task
* bounds is large enough for at least one split rule.
*/
+ @GuardedBy("mLock")
private void updateAnimationOverride(@NonNull TaskContainer taskContainer) {
if (ENABLE_SHELL_TRANSITIONS) {
// TODO(b/207070762): cleanup with legacy app transition
// Animation will be handled by WM Shell with Shell transition enabled.
return;
}
- if (!taskContainer.isTaskBoundsInitialized()
- || !taskContainer.isWindowingModeInitialized()) {
+ if (!taskContainer.isTaskBoundsInitialized()) {
// We don't know about the Task bounds/windowingMode yet.
return;
}
- // We only want to override if it supports split.
- if (supportSplit(taskContainer)) {
+ // We only want to override if the TaskContainer may show split.
+ if (mayShowSplit(taskContainer)) {
mPresenter.startOverrideSplitAnimation(taskContainer.getTaskId());
} else {
mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
}
}
- private boolean supportSplit(@NonNull TaskContainer taskContainer) {
+ /** Returns whether the given {@link TaskContainer} may show in split. */
+ // Suppress GuardedBy warning because lint asks to mark this method as
+ // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mLock")
+ private boolean mayShowSplit(@NonNull TaskContainer taskContainer) {
// No split inside PIP.
if (taskContainer.isInPictureInPicture()) {
return false;
}
+ // Always assume the TaskContainer if SplitAttributesCalculator is set
+ if (mSplitAttributesCalculator != null) {
+ return true;
+ }
// Check if the parent container bounds can support any split rule.
for (EmbeddingRule rule : mSplitRules) {
if (!(rule instanceof SplitRule)) {
continue;
}
- if (shouldShowSideBySide(taskContainer.getTaskBounds(), (SplitRule) rule)) {
+ final SplitRule splitRule = (SplitRule) rule;
+ final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(
+ taskContainer.getTaskProperties(), splitRule, null /* minDimensionsPair */);
+ if (shouldShowSplit(splitAttributes)) {
return true;
}
}
@@ -700,14 +796,18 @@
/**
* Starts an activity to side of the launchingActivity with the provided split config.
*/
+ // Suppress GuardedBy warning because lint ask to mark this method as
+ // @GuardedBy(container.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
@GuardedBy("mLock")
private void startActivityToSide(@NonNull WindowContainerTransaction wct,
@NonNull Activity launchingActivity, @NonNull Intent intent,
@Nullable Bundle options, @NonNull SplitRule sideRule,
- @Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) {
+ @NonNull SplitAttributes splitAttributes, @Nullable Consumer<Exception> failureCallback,
+ boolean isPlaceholder) {
try {
mPresenter.startActivityToSide(wct, launchingActivity, intent, options, sideRule,
- isPlaceholder);
+ splitAttributes, isPlaceholder);
} catch (Exception e) {
if (failureCallback != null) {
failureCallback.accept(e);
@@ -734,6 +834,10 @@
}
/** Whether the given new launched activity is in a split with a rule matched. */
+ // Suppress GuardedBy warning because lint asks to mark this method as
+ // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mLock")
private boolean isNewActivityInSplitWithRuleMatched(@NonNull Activity launchedActivity) {
final TaskFragmentContainer container = getContainerWithActivity(launchedActivity);
final SplitContainer splitContainer = getActiveSplitForContainer(container);
@@ -827,8 +931,9 @@
final TaskFragmentContainer primaryContainer = getContainerWithActivity(
primaryActivity);
final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer);
+ final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity);
if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
- && canReuseContainer(splitRule, splitContainer.getSplitRule())) {
+ && canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)) {
// Can launch in the existing secondary container if the rules share the same
// presentation.
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
@@ -958,6 +1063,7 @@
*/
@VisibleForTesting
@Nullable
+ @GuardedBy("mLock")
TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
/*
@@ -1020,6 +1126,7 @@
/**
* Returns an empty expanded {@link TaskFragmentContainer} that we can launch an activity into.
*/
+ @GuardedBy("mLock")
@Nullable
private TaskFragmentContainer createEmptyExpandedContainer(
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
@@ -1061,8 +1168,9 @@
}
final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity);
final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
+ final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity);
if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer()
- && (canReuseContainer(splitRule, splitContainer.getSplitRule())
+ && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)
// TODO(b/231845476) we should always respect clearTop.
|| !respectClearTop)
&& mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
@@ -1101,12 +1209,14 @@
return newContainer(pendingAppearedActivity, pendingAppearedActivity, taskId);
}
+ @GuardedBy("mLock")
TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
@NonNull Activity activityInTask, int taskId) {
return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
activityInTask, taskId);
}
+ @GuardedBy("mLock")
TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
@NonNull Activity activityInTask, int taskId) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
@@ -1130,7 +1240,7 @@
throw new IllegalArgumentException("activityInTask must not be null,");
}
if (!mTaskContainers.contains(taskId)) {
- mTaskContainers.put(taskId, new TaskContainer(taskId));
+ mTaskContainers.put(taskId, new TaskContainer(taskId, activityInTask));
}
final TaskContainer taskContainer = mTaskContainers.get(taskId);
final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
@@ -1142,10 +1252,6 @@
Log.w(TAG, "Can't find bounds from activity=" + activityInTask);
}
}
- if (!taskContainer.isWindowingModeInitialized()) {
- taskContainer.setWindowingMode(activityInTask.getResources().getConfiguration()
- .windowConfiguration.getWindowingMode());
- }
updateAnimationOverride(taskContainer);
return container;
}
@@ -1154,12 +1260,16 @@
* Creates and registers a new split with the provided containers and configuration. Finishes
* existing secondary containers if found for the given primary container.
*/
+ // Suppress GuardedBy warning because lint ask to mark this method as
+ // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mLock")
void registerSplit(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity,
@NonNull TaskFragmentContainer secondaryContainer,
- @NonNull SplitRule splitRule) {
+ @NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes) {
final SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity,
- secondaryContainer, splitRule);
+ secondaryContainer, splitRule, splitAttributes);
// Remove container later to prevent pinning escaping toast showing in lock task mode.
if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
removeExistingSecondaryContainers(wct, primaryContainer);
@@ -1310,6 +1420,12 @@
// Skip position update - one or both containers are finished.
return;
}
+ final TaskContainer taskContainer = splitContainer.getTaskContainer();
+ final SplitRule splitRule = splitContainer.getSplitRule();
+ final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair();
+ final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(
+ taskContainer.getTaskProperties(), splitRule, minDimensionsPair);
+ splitContainer.setSplitAttributes(splitAttributes);
if (dismissPlaceholderIfNecessary(wct, splitContainer)) {
// Placeholder was finished, the positions will be updated when its container is emptied
return;
@@ -1383,6 +1499,9 @@
return launchPlaceholderIfNecessary(wct, topActivity, false /* isOnCreated */);
}
+ // Suppress GuardedBy warning because lint ask to mark this method as
+ // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
@GuardedBy("mLock")
boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
@NonNull Activity activity, boolean isOnCreated) {
@@ -1409,18 +1528,20 @@
return false;
}
+ final TaskContainer.TaskProperties taskProperties = mPresenter.getTaskProperties(activity);
final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity,
placeholderRule.getPlaceholderIntent());
- if (!shouldShowSideBySide(
- mPresenter.getParentContainerBounds(activity), placeholderRule,
- minDimensionsPair)) {
+ final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(taskProperties,
+ placeholderRule, minDimensionsPair);
+ if (!SplitPresenter.shouldShowSplit(splitAttributes)) {
return false;
}
// TODO(b/190433398): Handle failed request
final Bundle options = getPlaceholderOptions(activity, isOnCreated);
startActivityToSide(wct, activity, placeholderRule.getPlaceholderIntent(), options,
- placeholderRule, null /* failureCallback */, true /* isPlaceholder */);
+ placeholderRule, splitAttributes, null /* failureCallback */,
+ true /* isPlaceholder */);
return true;
}
@@ -1445,6 +1566,9 @@
return options.toBundle();
}
+ // Suppress GuardedBy warning because lint ask to mark this method as
+ // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
@VisibleForTesting
@GuardedBy("mLock")
boolean dismissPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
@@ -1457,11 +1581,10 @@
// The placeholder should remain after it was first shown.
return false;
}
-
- if (shouldShowSideBySide(splitContainer)) {
+ final SplitAttributes splitAttributes = splitContainer.getSplitAttributes();
+ if (SplitPresenter.shouldShowSplit(splitAttributes)) {
return false;
}
-
mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(),
false /* shouldFinishDependent */);
return true;
@@ -1471,6 +1594,7 @@
* Returns the rule to launch a placeholder for the activity with the provided component name
* if it is configured in the split config.
*/
+ @GuardedBy("mLock")
private SplitPlaceholderRule getPlaceholderRule(@NonNull Activity activity) {
for (EmbeddingRule rule : mSplitRules) {
if (!(rule instanceof SplitPlaceholderRule)) {
@@ -1487,6 +1611,7 @@
/**
* Notifies listeners about changes to split states if necessary.
*/
+ @GuardedBy("mLock")
private void updateCallbackIfNecessary() {
if (mEmbeddingCallback == null) {
return;
@@ -1508,6 +1633,7 @@
* null, that indicates that the active split states are in an intermediate state and should
* not be reported.
*/
+ @GuardedBy("mLock")
@Nullable
private List<SplitInfo> getActiveSplitStates() {
List<SplitInfo> splitStates = new ArrayList<>();
@@ -1526,20 +1652,8 @@
.toActivityStack();
final ActivityStack secondaryContainer = container.getSecondaryContainer()
.toActivityStack();
- final SplitAttributes.SplitType splitType = shouldShowSideBySide(container)
- ? new SplitAttributes.SplitType.RatioSplitType(
- container.getSplitRule().getSplitRatio())
- : new SplitAttributes.SplitType.ExpandContainersSplitType();
final SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer,
- // Splits that are not showing side-by-side are reported as having 0 split
- // ratio, since by definition in the API the primary container occupies no
- // width of the split when covered by the secondary.
- // TODO(b/241042437): use v2 APIs for splitAttributes
- new SplitAttributes.Builder()
- .setSplitType(splitType)
- .setLayoutDirection(container.getSplitRule().getLayoutDirection())
- .build()
- );
+ container.getSplitAttributes());
splitStates.add(splitState);
}
}
@@ -1577,6 +1691,7 @@
* Returns a split rule for the provided pair of primary activity and secondary activity intent
* if available.
*/
+ @GuardedBy("mLock")
@Nullable
private SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
@NonNull Intent secondaryActivityIntent) {
@@ -1595,6 +1710,7 @@
/**
* Returns a split rule for the provided pair of primary and secondary activities if available.
*/
+ @GuardedBy("mLock")
@Nullable
private SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity) {
@@ -1669,6 +1785,7 @@
* Returns {@code true} if an Activity with the provided component name should always be
* expanded to occupy full task bounds. Such activity must not be put in a split.
*/
+ @GuardedBy("mLock")
private boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
for (EmbeddingRule rule : mSplitRules) {
if (!(rule instanceof ActivityRule)) {
@@ -1694,6 +1811,10 @@
* 'sticky' and the placeholder was finished when fully overlapping the primary container.
* @return {@code true} if the associated container should be retained (and not be finished).
*/
+ // Suppress GuardedBy warning because lint ask to mark this method as
+ // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mLock")
boolean shouldRetainAssociatedContainer(@NonNull TaskFragmentContainer finishingContainer,
@NonNull TaskFragmentContainer associatedContainer) {
SplitContainer splitContainer = getActiveSplitForContainers(associatedContainer,
@@ -1712,7 +1833,7 @@
}
// Decide whether the associated container should be retained based on the current
// presentation mode.
- if (shouldShowSideBySide(splitContainer)) {
+ if (shouldShowSplit(splitContainer)) {
return !shouldFinishAssociatedContainerWhenAdjacent(finishBehavior);
} else {
return !shouldFinishAssociatedContainerWhenStacked(finishBehavior);
@@ -1905,23 +2026,33 @@
* If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if
* there is any.
*/
- private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2) {
+ private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2,
+ @NonNull WindowMetrics parentWindowMetrics) {
if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) {
return false;
}
- return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2);
+ return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2,
+ parentWindowMetrics);
}
/** Whether the two rules have the same presentation. */
- private static boolean haveSamePresentation(@NonNull SplitPairRule rule1,
- @NonNull SplitPairRule rule2) {
+ @VisibleForTesting
+ static boolean haveSamePresentation(@NonNull SplitPairRule rule1,
+ @NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) {
+ if (rule1.getTag() != null || rule2.getTag() != null) {
+ // Tag must be unique if it is set. We don't want to reuse the container if the rules
+ // have different tags because they can have different SplitAttributes later through
+ // SplitAttributesCalculator.
+ return Objects.equals(rule1.getTag(), rule2.getTag());
+ }
+ // If both rules don't have tag, compare all SplitRules' properties that may affect their
+ // SplitAttributes.
// TODO(b/231655482): add util method to do the comparison in SplitPairRule.
- return rule1.getSplitRatio() == rule2.getSplitRatio()
- && rule1.getLayoutDirection() == rule2.getLayoutDirection()
- && rule1.getFinishPrimaryWithSecondary()
- == rule2.getFinishPrimaryWithSecondary()
- && rule1.getFinishSecondaryWithPrimary()
- == rule2.getFinishSecondaryWithPrimary();
+ return rule1.getDefaultSplitAttributes().equals(rule2.getDefaultSplitAttributes())
+ && rule1.checkParentMetrics(parentWindowMetrics)
+ == rule2.checkParentMetrics(parentWindowMetrics)
+ && rule1.getFinishPrimaryWithSecondary() == rule2.getFinishPrimaryWithSecondary()
+ && rule1.getFinishSecondaryWithPrimary() == rule2.getFinishSecondaryWithPrimary();
}
/**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 2ef8e4c..7960323 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -22,11 +22,11 @@
import android.app.ActivityThread;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
@@ -42,9 +42,21 @@
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.HingeSplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
+import androidx.window.extensions.embedding.SplitAttributesCalculator.SplitAttributesCalculatorParams;
+import androidx.window.extensions.embedding.TaskContainer.TaskProperties;
+import androidx.window.extensions.layout.DisplayFeature;
+import androidx.window.extensions.layout.FoldingFeature;
+import androidx.window.extensions.layout.WindowLayoutInfo;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -66,11 +78,25 @@
})
private @interface Position {}
+ private static final int CONTAINER_POSITION_LEFT = 0;
+ private static final int CONTAINER_POSITION_TOP = 1;
+ private static final int CONTAINER_POSITION_RIGHT = 2;
+ private static final int CONTAINER_POSITION_BOTTOM = 3;
+
+ @IntDef(value = {
+ CONTAINER_POSITION_LEFT,
+ CONTAINER_POSITION_TOP,
+ CONTAINER_POSITION_RIGHT,
+ CONTAINER_POSITION_BOTTOM,
+ })
+ private @interface ContainerPosition {}
+
/**
* Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer,
* Activity, Activity, Intent)}.
* No need to expand the splitContainer because screen is big enough to
- * {@link #shouldShowSideBySide(Rect, SplitRule, Pair)} and minimum dimensions is satisfied.
+ * {@link #shouldShowSplit(SplitAttributes)} and minimum dimensions is
+ * satisfied.
*/
static final int RESULT_NOT_EXPANDED = 0;
/**
@@ -78,7 +104,7 @@
* Activity, Activity, Intent)}.
* The splitContainer should be expanded. It is usually because minimum dimensions is not
* satisfied.
- * @see #shouldShowSideBySide(Rect, SplitRule, Pair)
+ * @see #shouldShowSplit(SplitAttributes)
*/
static final int RESULT_EXPANDED = 1;
/**
@@ -101,6 +127,12 @@
})
private @interface ResultCode {}
+ @VisibleForTesting
+ static final SplitAttributes EXPAND_CONTAINERS_ATTRIBUTES =
+ new SplitAttributes.Builder()
+ .setSplitType(new ExpandContainersSplitType())
+ .build();
+
private final SplitController mController;
SplitPresenter(@NonNull Executor executor, @NonNull SplitController controller) {
@@ -129,14 +161,17 @@
* @return The newly created secondary container.
*/
@NonNull
+ @GuardedBy("mController.mLock")
TaskFragmentContainer createNewSplitWithEmptySideContainer(
@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
@NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) {
- final Rect parentBounds = getParentContainerBounds(primaryActivity);
+ final TaskProperties taskProperties = getTaskProperties(primaryActivity);
final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
primaryActivity, secondaryIntent);
- final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
- primaryActivity, minDimensionsPair);
+ final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
+ minDimensionsPair);
+ final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
primaryActivity, primaryRectBounds, null);
@@ -144,8 +179,8 @@
final int taskId = primaryContainer.getTaskId();
final TaskFragmentContainer secondaryContainer = mController.newContainer(
secondaryIntent, primaryActivity, taskId);
- final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds,
- rule, primaryActivity, minDimensionsPair);
+ final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
+ splitAttributes);
final int windowingMode = mController.getTaskContainer(taskId)
.getWindowingModeForSplitTaskFragment(secondaryRectBounds);
createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
@@ -154,9 +189,10 @@
// Set adjacent to each other so that the containers below will be invisible.
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
- minDimensionsPair);
+ splitAttributes);
- mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
+ mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule,
+ splitAttributes);
return secondaryContainer;
}
@@ -176,16 +212,18 @@
void createNewSplitContainer(@NonNull WindowContainerTransaction wct,
@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity,
@NonNull SplitPairRule rule) {
- final Rect parentBounds = getParentContainerBounds(primaryActivity);
+ final TaskProperties taskProperties = getTaskProperties(primaryActivity);
final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity,
secondaryActivity);
- final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
- primaryActivity, minDimensionsPair);
+ final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
+ minDimensionsPair);
+ final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
primaryActivity, primaryRectBounds, null);
- final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
- primaryActivity, minDimensionsPair);
+ final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
+ splitAttributes);
final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity(
secondaryActivity);
TaskFragmentContainer containerToAvoid = primaryContainer;
@@ -200,9 +238,10 @@
// Set adjacent to each other so that the containers below will be invisible.
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
- minDimensionsPair);
+ splitAttributes);
- mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
+ mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule,
+ splitAttributes);
}
/**
@@ -244,16 +283,16 @@
* @param rule The split rule to be applied to the container.
* @param isPlaceholder Whether the launch is a placeholder.
*/
+ @GuardedBy("mController.mLock")
void startActivityToSide(@NonNull WindowContainerTransaction wct,
@NonNull Activity launchingActivity, @NonNull Intent activityIntent,
- @Nullable Bundle activityOptions, @NonNull SplitRule rule, boolean isPlaceholder) {
- final Rect parentBounds = getParentContainerBounds(launchingActivity);
- final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
- launchingActivity, activityIntent);
- final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
- launchingActivity, minDimensionsPair);
- final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
- launchingActivity, minDimensionsPair);
+ @Nullable Bundle activityOptions, @NonNull SplitRule rule,
+ @NonNull SplitAttributes splitAttributes, boolean isPlaceholder) {
+ final TaskProperties taskProperties = getTaskProperties(launchingActivity);
+ final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes);
+ final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
+ splitAttributes);
TaskFragmentContainer primaryContainer = mController.getContainerWithActivity(
launchingActivity);
@@ -268,7 +307,7 @@
final int windowingMode = mController.getTaskContainer(taskId)
.getWindowingModeForSplitTaskFragment(primaryRectBounds);
mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
- rule);
+ rule, splitAttributes);
startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds,
activityIntent, activityOptions, rule, windowingMode);
@@ -284,22 +323,24 @@
* @param updatedContainer The task fragment that was updated and caused this split update.
* @param wct WindowContainerTransaction that this update should be performed with.
*/
+ @GuardedBy("mController.mLock")
void updateSplitContainer(@NonNull SplitContainer splitContainer,
@NonNull TaskFragmentContainer updatedContainer,
@NonNull WindowContainerTransaction wct) {
- // Getting the parent bounds using the updated container - it will have the recent value.
- final Rect parentBounds = getParentContainerBounds(updatedContainer);
+ // Getting the parent configuration using the updated container - it will have the recent
+ // value.
final SplitRule rule = splitContainer.getSplitRule();
final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer();
final Activity activity = primaryContainer.getTopNonFinishingActivity();
if (activity == null) {
return;
}
- final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair();
- final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
- activity, minDimensionsPair);
- final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
- activity, minDimensionsPair);
+ final TaskProperties taskProperties = getTaskProperties(updatedContainer);
+ final SplitAttributes splitAttributes = splitContainer.getSplitAttributes();
+ final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes);
+ final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
+ splitAttributes);
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
// Whether the placeholder is becoming side-by-side with the primary from fullscreen.
final boolean isPlaceholderBecomingSplit = splitContainer.isPlaceholderContainer()
@@ -311,7 +352,7 @@
resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds);
resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds);
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
- minDimensionsPair);
+ splitAttributes);
if (isPlaceholderBecomingSplit) {
// When placeholder is shown in split, we should keep the focus on the primary.
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
@@ -323,14 +364,14 @@
updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode);
}
+ @GuardedBy("mController.mLock")
private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer primaryContainer,
@NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule,
- @NonNull Pair<Size, Size> minDimensionsPair) {
- final Rect parentBounds = getParentContainerBounds(primaryContainer);
+ @NonNull SplitAttributes splitAttributes) {
// Clear adjacent TaskFragments if the container is shown in fullscreen, or the
// secondaryContainer could not be finished.
- if (!shouldShowSideBySide(parentBounds, splitRule, minDimensionsPair)) {
+ if (!shouldShowSplit(splitAttributes)) {
setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
null /* secondary */, null /* splitRule */);
} else {
@@ -416,8 +457,9 @@
* Expands the split container if the current split bounds are smaller than the Activity or
* Intent that is added to the container.
*
- * @return the {@link ResultCode} based on {@link #shouldShowSideBySide(Rect, SplitRule, Pair)}
- * and if {@link android.window.TaskFragmentInfo} has reported to the client side.
+ * @return the {@link ResultCode} based on
+ * {@link #shouldShowSplit(SplitAttributes)} and if
+ * {@link android.window.TaskFragmentInfo} has reported to the client side.
*/
@ResultCode
int expandSplitContainerIfNeeded(@NonNull WindowContainerTransaction wct,
@@ -427,7 +469,6 @@
throw new IllegalArgumentException("Either secondaryActivity or secondaryIntent must be"
+ " non-null.");
}
- final Rect taskBounds = getParentContainerBounds(primaryActivity);
final Pair<Size, Size> minDimensionsPair;
if (secondaryActivity != null) {
minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity);
@@ -436,7 +477,12 @@
secondaryIntent);
}
// Expand the splitContainer if minimum dimensions are not satisfied.
- if (!shouldShowSideBySide(taskBounds, splitContainer.getSplitRule(), minDimensionsPair)) {
+ final TaskContainer taskContainer = splitContainer.getTaskContainer();
+ final SplitAttributes splitAttributes = sanitizeSplitAttributes(
+ taskContainer.getTaskProperties(), splitContainer.getSplitAttributes(),
+ minDimensionsPair);
+ splitContainer.setSplitAttributes(splitAttributes);
+ if (!shouldShowSplit(splitAttributes)) {
// If the client side hasn't received TaskFragmentInfo yet, we can't change TaskFragment
// bounds. Return failure to create a new SplitContainer which fills task bounds.
if (splitContainer.getPrimaryContainer().getInfo() == null
@@ -450,36 +496,63 @@
return RESULT_NOT_EXPANDED;
}
- static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule) {
- return shouldShowSideBySide(parentBounds, rule, null /* minimumDimensionPair */);
+ static boolean shouldShowSplit(@NonNull SplitContainer splitContainer) {
+ return shouldShowSplit(splitContainer.getSplitAttributes());
}
- static boolean shouldShowSideBySide(@NonNull SplitContainer splitContainer) {
- final Rect parentBounds = getParentContainerBounds(splitContainer.getPrimaryContainer());
-
- return shouldShowSideBySide(parentBounds, splitContainer.getSplitRule(),
- splitContainer.getMinDimensionsPair());
+ static boolean shouldShowSplit(@NonNull SplitAttributes splitAttributes) {
+ return !(splitAttributes.getSplitType() instanceof ExpandContainersSplitType);
}
- static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule,
+ @GuardedBy("mController.mLock")
+ @NonNull
+ SplitAttributes computeSplitAttributes(@NonNull TaskProperties taskProperties,
+ @NonNull SplitRule rule, @Nullable Pair<Size, Size> minDimensionsPair) {
+ final Configuration taskConfiguration = taskProperties.getConfiguration();
+ final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration);
+ final SplitAttributesCalculator calculator = mController.getSplitAttributesCalculator();
+ final SplitAttributes defaultSplitAttributes = rule.getDefaultSplitAttributes();
+ final boolean isDefaultMinSizeSatisfied = rule.checkParentMetrics(taskWindowMetrics);
+ if (calculator == null) {
+ if (!isDefaultMinSizeSatisfied) {
+ return EXPAND_CONTAINERS_ATTRIBUTES;
+ }
+ return sanitizeSplitAttributes(taskProperties, defaultSplitAttributes,
+ minDimensionsPair);
+ }
+ final WindowLayoutInfo windowLayoutInfo = mController.mWindowLayoutComponent
+ .getCurrentWindowLayoutInfo(taskProperties.getDisplayId(),
+ taskConfiguration.windowConfiguration);
+ final SplitAttributesCalculatorParams params = new SplitAttributesCalculatorParams(
+ taskWindowMetrics, taskConfiguration, defaultSplitAttributes,
+ isDefaultMinSizeSatisfied, windowLayoutInfo, rule.getTag());
+ final SplitAttributes splitAttributes = calculator.computeSplitAttributesForParams(params);
+ return sanitizeSplitAttributes(taskProperties, splitAttributes, minDimensionsPair);
+ }
+
+ /**
+ * Returns {@link #EXPAND_CONTAINERS_ATTRIBUTES} if the passed {@link SplitAttributes} doesn't
+ * meet the minimum dimensions set in {@link ActivityInfo.WindowLayout}. Otherwise, returns
+ * the passed {@link SplitAttributes}.
+ */
+ @NonNull
+ private SplitAttributes sanitizeSplitAttributes(@NonNull TaskProperties taskProperties,
+ @NonNull SplitAttributes splitAttributes,
@Nullable Pair<Size, Size> minDimensionsPair) {
- // TODO(b/190433398): Supply correct insets.
- final WindowMetrics parentMetrics = new WindowMetrics(parentBounds,
- new WindowInsets(new Rect()));
- // Don't show side by side if bounds is not qualified.
- if (!rule.checkParentMetrics(parentMetrics)) {
- return false;
- }
- final float splitRatio = rule.getSplitRatio();
- // We only care the size of the bounds regardless of its position.
- final Rect primaryBounds = getPrimaryBounds(parentBounds, splitRatio, true /* isLtr */);
- final Rect secondaryBounds = getSecondaryBounds(parentBounds, splitRatio, true /* isLtr */);
-
if (minDimensionsPair == null) {
- return true;
+ return splitAttributes;
}
- return !boundsSmallerThanMinDimensions(primaryBounds, minDimensionsPair.first)
- && !boundsSmallerThanMinDimensions(secondaryBounds, minDimensionsPair.second);
+ final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
+ final Configuration taskConfiguration = taskProperties.getConfiguration();
+ final Rect primaryBounds = getPrimaryBounds(taskConfiguration, splitAttributes,
+ foldingFeature);
+ final Rect secondaryBounds = getSecondaryBounds(taskConfiguration, splitAttributes,
+ foldingFeature);
+ if (boundsSmallerThanMinDimensions(primaryBounds, minDimensionsPair.first)
+ || boundsSmallerThanMinDimensions(secondaryBounds, minDimensionsPair.second)) {
+ return EXPAND_CONTAINERS_ATTRIBUTES;
+ }
+ return splitAttributes;
}
@NonNull
@@ -541,20 +614,25 @@
@VisibleForTesting
@NonNull
- static Rect getBoundsForPosition(@Position int position, @NonNull Rect parentBounds,
- @NonNull SplitRule rule, @NonNull Activity primaryActivity,
- @Nullable Pair<Size, Size> minDimensionsPair) {
- if (!shouldShowSideBySide(parentBounds, rule, minDimensionsPair)) {
+ Rect getBoundsForPosition(@Position int position, @NonNull TaskProperties taskProperties,
+ @NonNull SplitAttributes splitAttributes) {
+ final Configuration taskConfiguration = taskProperties.getConfiguration();
+ final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
+ final SplitType splitType = computeSplitType(splitAttributes, taskConfiguration,
+ foldingFeature);
+ final SplitAttributes computedSplitAttributes = new SplitAttributes.Builder()
+ .setSplitType(splitType)
+ .setLayoutDirection(splitAttributes.getLayoutDirection())
+ .build();
+ if (!shouldShowSplit(computedSplitAttributes)) {
return new Rect();
}
- final boolean isLtr = isLtr(primaryActivity, rule);
- final float splitRatio = rule.getSplitRatio();
-
switch (position) {
case POSITION_START:
- return getPrimaryBounds(parentBounds, splitRatio, isLtr);
+ return getPrimaryBounds(taskConfiguration, computedSplitAttributes, foldingFeature);
case POSITION_END:
- return getSecondaryBounds(parentBounds, splitRatio, isLtr);
+ return getSecondaryBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature);
case POSITION_FILL:
default:
return new Rect();
@@ -562,74 +640,303 @@
}
@NonNull
- private static Rect getPrimaryBounds(@NonNull Rect parentBounds, float splitRatio,
- boolean isLtr) {
- return isLtr ? getLeftContainerBounds(parentBounds, splitRatio)
- : getRightContainerBounds(parentBounds, 1 - splitRatio);
- }
-
- @NonNull
- private static Rect getSecondaryBounds(@NonNull Rect parentBounds, float splitRatio,
- boolean isLtr) {
- return isLtr ? getRightContainerBounds(parentBounds, splitRatio)
- : getLeftContainerBounds(parentBounds, 1 - splitRatio);
- }
-
- private static Rect getLeftContainerBounds(@NonNull Rect parentBounds, float splitRatio) {
- return new Rect(
- parentBounds.left,
- parentBounds.top,
- (int) (parentBounds.left + parentBounds.width() * splitRatio),
- parentBounds.bottom);
- }
-
- private static Rect getRightContainerBounds(@NonNull Rect parentBounds, float splitRatio) {
- return new Rect(
- (int) (parentBounds.left + parentBounds.width() * splitRatio),
- parentBounds.top,
- parentBounds.right,
- parentBounds.bottom);
- }
-
- /**
- * Checks if a split with the provided rule should be displays in left-to-right layout
- * direction, either always or with the current configuration.
- */
- private static boolean isLtr(@NonNull Context context, @NonNull SplitRule rule) {
- switch (rule.getLayoutDirection()) {
- case LayoutDirection.LOCALE:
- return context.getResources().getConfiguration().getLayoutDirection()
+ private Rect getPrimaryBounds(@NonNull Configuration taskConfiguration,
+ @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+ if (!shouldShowSplit(splitAttributes)) {
+ return new Rect();
+ }
+ switch (splitAttributes.getLayoutDirection()) {
+ case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: {
+ return getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ }
+ case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: {
+ return getRightContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ }
+ case SplitAttributes.LayoutDirection.LOCALE: {
+ final boolean isLtr = taskConfiguration.getLayoutDirection()
== View.LAYOUT_DIRECTION_LTR;
- case LayoutDirection.RTL:
- return false;
- case LayoutDirection.LTR:
+ return isLtr
+ ? getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature)
+ : getRightContainerBounds(taskConfiguration, splitAttributes,
+ foldingFeature);
+ }
+ case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: {
+ return getTopContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ }
+ case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: {
+ return getBottomContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ }
default:
- return true;
+ throw new IllegalArgumentException("Unknown layout direction:"
+ + splitAttributes.getLayoutDirection());
}
}
@NonNull
- static Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) {
- return container.getTaskContainer().getTaskBounds();
+ private Rect getSecondaryBounds(@NonNull Configuration taskConfiguration,
+ @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+ if (!shouldShowSplit(splitAttributes)) {
+ return new Rect();
+ }
+ switch (splitAttributes.getLayoutDirection()) {
+ case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: {
+ return getRightContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ }
+ case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: {
+ return getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ }
+ case SplitAttributes.LayoutDirection.LOCALE: {
+ final boolean isLtr = taskConfiguration.getLayoutDirection()
+ == View.LAYOUT_DIRECTION_LTR;
+ return isLtr
+ ? getRightContainerBounds(taskConfiguration, splitAttributes,
+ foldingFeature)
+ : getLeftContainerBounds(taskConfiguration, splitAttributes,
+ foldingFeature);
+ }
+ case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: {
+ return getBottomContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ }
+ case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: {
+ return getTopContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ }
+ default:
+ throw new IllegalArgumentException("Unknown layout direction:"
+ + splitAttributes.getLayoutDirection());
+ }
}
@NonNull
- Rect getParentContainerBounds(@NonNull Activity activity) {
- final TaskFragmentContainer container = mController.getContainerWithActivity(activity);
- if (container != null) {
- return getParentContainerBounds(container);
- }
- // Obtain bounds from Activity instead because the Activity hasn't been embedded yet.
- return getNonEmbeddedActivityBounds(activity);
+ private Rect getLeftContainerBounds(@NonNull Configuration taskConfiguration,
+ @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+ final int right = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
+ CONTAINER_POSITION_LEFT, foldingFeature);
+ final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
+ return new Rect(taskBounds.left, taskBounds.top, right, taskBounds.bottom);
+ }
+
+ @NonNull
+ private Rect getRightContainerBounds(@NonNull Configuration taskConfiguration,
+ @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+ final int left = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
+ CONTAINER_POSITION_RIGHT, foldingFeature);
+ final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
+ return new Rect(left, parentBounds.top, parentBounds.right, parentBounds.bottom);
+ }
+
+ @NonNull
+ private Rect getTopContainerBounds(@NonNull Configuration taskConfiguration,
+ @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+ final int bottom = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
+ CONTAINER_POSITION_TOP, foldingFeature);
+ final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
+ return new Rect(parentBounds.left, parentBounds.top, parentBounds.right, bottom);
+ }
+
+ @NonNull
+ private Rect getBottomContainerBounds(@NonNull Configuration taskConfiguration,
+ @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+ final int top = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
+ CONTAINER_POSITION_BOTTOM, foldingFeature);
+ final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
+ return new Rect(parentBounds.left, top, parentBounds.right, parentBounds.bottom);
}
/**
- * Obtains the bounds from a non-embedded Activity.
- * <p>
- * Note that callers should use {@link #getParentContainerBounds(Activity)} instead for most
- * cases unless we want to obtain task bounds before
- * {@link TaskContainer#isTaskBoundsInitialized()}.
+ * Computes the boundary position between the primary and the secondary containers for the given
+ * {@link ContainerPosition} with {@link SplitAttributes}, current window and device states.
+ * <ol>
+ * <li>For {@link #CONTAINER_POSITION_TOP}, it computes the boundary with the bottom
+ * container, which is {@link Rect#bottom} of the top container bounds.</li>
+ * <li>For {@link #CONTAINER_POSITION_BOTTOM}, it computes the boundary with the top
+ * container, which is {@link Rect#top} of the bottom container bounds.</li>
+ * <li>For {@link #CONTAINER_POSITION_LEFT}, it computes the boundary with the right
+ * container, which is {@link Rect#right} of the left container bounds.</li>
+ * <li>For {@link #CONTAINER_POSITION_RIGHT}, it computes the boundary with the bottom
+ * container, which is {@link Rect#left} of the right container bounds.</li>
+ * </ol>
+ *
+ * @see #getTopContainerBounds(Configuration, SplitAttributes, FoldingFeature)
+ * @see #getBottomContainerBounds(Configuration, SplitAttributes, FoldingFeature)
+ * @see #getLeftContainerBounds(Configuration, SplitAttributes, FoldingFeature)
+ * @see #getRightContainerBounds(Configuration, SplitAttributes, FoldingFeature)
*/
+ private int computeBoundaryBetweenContainers(@NonNull Configuration taskConfiguration,
+ @NonNull SplitAttributes splitAttributes, @ContainerPosition int position,
+ @Nullable FoldingFeature foldingFeature) {
+ final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
+ final int startPoint = shouldSplitHorizontally(splitAttributes)
+ ? parentBounds.top
+ : parentBounds.left;
+ final int dimen = shouldSplitHorizontally(splitAttributes)
+ ? parentBounds.height()
+ : parentBounds.width();
+ final SplitType splitType = splitAttributes.getSplitType();
+ if (splitType instanceof RatioSplitType) {
+ final RatioSplitType splitRatio = (RatioSplitType) splitType;
+ return (int) (startPoint + dimen * splitRatio.getRatio());
+ }
+ // At this point, SplitType must be a HingeSplitType and foldingFeature must be
+ // non-null. RatioSplitType and ExpandContainerSplitType have been handled earlier.
+ Objects.requireNonNull(foldingFeature);
+ if (!(splitType instanceof HingeSplitType)) {
+ throw new IllegalArgumentException("Unknown splitType:" + splitType);
+ }
+ final Rect hingeArea = foldingFeature.getBounds();
+ switch (position) {
+ case CONTAINER_POSITION_LEFT:
+ return hingeArea.left;
+ case CONTAINER_POSITION_TOP:
+ return hingeArea.top;
+ case CONTAINER_POSITION_RIGHT:
+ return hingeArea.right;
+ case CONTAINER_POSITION_BOTTOM:
+ return hingeArea.bottom;
+ default:
+ throw new IllegalArgumentException("Unknown position:" + position);
+ }
+ }
+
+ @Nullable
+ private FoldingFeature getFoldingFeature(@NonNull TaskProperties taskProperties) {
+ final int displayId = taskProperties.getDisplayId();
+ final WindowConfiguration windowConfiguration = taskProperties.getConfiguration()
+ .windowConfiguration;
+ final WindowLayoutInfo info = mController.mWindowLayoutComponent
+ .getCurrentWindowLayoutInfo(displayId, windowConfiguration);
+ final List<DisplayFeature> displayFeatures = info.getDisplayFeatures();
+ if (displayFeatures.isEmpty()) {
+ return null;
+ }
+ final List<FoldingFeature> foldingFeatures = new ArrayList<>();
+ for (DisplayFeature displayFeature : displayFeatures) {
+ if (displayFeature instanceof FoldingFeature) {
+ foldingFeatures.add((FoldingFeature) displayFeature);
+ }
+ }
+ // TODO(b/240219484): Support device with multiple hinges.
+ if (foldingFeatures.size() != 1) {
+ return null;
+ }
+ return foldingFeatures.get(0);
+ }
+
+ /**
+ * Indicates that this {@link SplitAttributes} splits the task horizontally. Returns
+ * {@code false} if this {@link SplitAttributes} splits the task vertically.
+ */
+ private static boolean shouldSplitHorizontally(SplitAttributes splitAttributes) {
+ switch (splitAttributes.getLayoutDirection()) {
+ case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM:
+ case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Computes the {@link SplitType} with the {@link SplitAttributes} and the current device and
+ * window state.
+ * If passed {@link SplitAttributes#getSplitType} is a {@link RatioSplitType}. It reversed
+ * the ratio if the computed {@link SplitAttributes#getLayoutDirection} is
+ * {@link SplitAttributes.LayoutDirection.LEFT_TO_RIGHT} or
+ * {@link SplitAttributes.LayoutDirection.BOTTOM_TO_TOP} to make the bounds calculation easier.
+ * If passed {@link SplitAttributes#getSplitType} is a {@link HingeSplitType}, it checks
+ * the current device and window states to determine whether the split container should split
+ * by hinge or use {@link HingeSplitType#getFallbackSplitType}.
+ */
+ private SplitType computeSplitType(@NonNull SplitAttributes splitAttributes,
+ @NonNull Configuration taskConfiguration, @Nullable FoldingFeature foldingFeature) {
+ final int layoutDirection = splitAttributes.getLayoutDirection();
+ final SplitType splitType = splitAttributes.getSplitType();
+ if (splitType instanceof ExpandContainersSplitType) {
+ return splitType;
+ } else if (splitType instanceof RatioSplitType) {
+ final RatioSplitType splitRatio = (RatioSplitType) splitType;
+ // Reverse the ratio for RIGHT_TO_LEFT and BOTTOM_TO_TOP to make the boundary
+ // computation have the same direction, which is from (top, left) to (bottom, right).
+ final SplitType reversedSplitType = new RatioSplitType(1 - splitRatio.getRatio());
+ switch (layoutDirection) {
+ case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT:
+ case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM:
+ return splitType;
+ case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT:
+ case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP:
+ return reversedSplitType;
+ case LayoutDirection.LOCALE: {
+ boolean isLtr = taskConfiguration.getLayoutDirection()
+ == View.LAYOUT_DIRECTION_LTR;
+ return isLtr ? splitType : reversedSplitType;
+ }
+ }
+ } else if (splitType instanceof HingeSplitType) {
+ final HingeSplitType hinge = (HingeSplitType) splitType;
+ @WindowingMode
+ final int windowingMode = taskConfiguration.windowConfiguration.getWindowingMode();
+ return shouldSplitByHinge(splitAttributes, foldingFeature, windowingMode)
+ ? hinge : hinge.getFallbackSplitType();
+ }
+ throw new IllegalArgumentException("Unknown SplitType:" + splitType);
+ }
+
+ private static boolean shouldSplitByHinge(@NonNull SplitAttributes splitAttributes,
+ @Nullable FoldingFeature foldingFeature, @WindowingMode int taskWindowingMode) {
+ // Only HingeSplitType may split the task bounds by hinge.
+ if (!(splitAttributes.getSplitType() instanceof HingeSplitType)) {
+ return false;
+ }
+ // Device is not foldable, so there's no hinge to match.
+ if (foldingFeature == null) {
+ return false;
+ }
+ // The task is in multi-window mode. Match hinge doesn't make sense because current task
+ // bounds may not fit display bounds.
+ if (WindowConfiguration.inMultiWindowMode(taskWindowingMode)) {
+ return false;
+ }
+ // Return true if how the split attributes split the task bounds matches the orientation of
+ // folding area orientation.
+ return shouldSplitHorizontally(splitAttributes) == isFoldingAreaHorizontal(foldingFeature);
+ }
+
+ private static boolean isFoldingAreaHorizontal(@NonNull FoldingFeature foldingFeature) {
+ final Rect bounds = foldingFeature.getBounds();
+ return bounds.width() > bounds.height();
+ }
+
+ @NonNull
+ static TaskProperties getTaskProperties(@NonNull TaskFragmentContainer container) {
+ return container.getTaskContainer().getTaskProperties();
+ }
+
+ @NonNull
+ TaskProperties getTaskProperties(@NonNull Activity activity) {
+ final TaskContainer taskContainer = mController.getTaskContainer(
+ mController.getTaskId(activity));
+ if (taskContainer != null) {
+ return taskContainer.getTaskProperties();
+ }
+ // Use a copy of configuration because activity's configuration may be updated later,
+ // or we may get unexpected TaskContainer's configuration if Activity's configuration is
+ // updated. An example is Activity is going to be in split.
+ return new TaskProperties(activity.getDisplayId(),
+ new Configuration(activity.getResources().getConfiguration()));
+ }
+
+ @NonNull
+ WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) {
+ return getTaskWindowMetrics(getTaskProperties(activity).getConfiguration());
+ }
+
+ @NonNull
+ private static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) {
+ final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
+ // TODO(b/190433398): Supply correct insets.
+ return new WindowMetrics(taskBounds, WindowInsets.CONSUMED);
+ }
+
+ /** Obtains the bounds from a non-embedded Activity. */
@NonNull
static Rect getNonEmbeddedActivityBounds(@NonNull Activity activity) {
final WindowConfiguration windowConfiguration =
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index b563677..91573ff 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -24,10 +24,13 @@
import android.app.Activity;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArraySet;
import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentParentInfo;
+import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -42,13 +45,10 @@
/** The unique task id. */
private final int mTaskId;
+ // TODO(b/240219484): consolidate to mConfiguration
/** Available window bounds of this Task. */
private final Rect mTaskBounds = new Rect();
- /** Windowing mode of this Task. */
- @WindowingMode
- private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
-
/** Active TaskFragments in this Task. */
@NonNull
final List<TaskFragmentContainer> mContainers = new ArrayList<>();
@@ -57,24 +57,56 @@
@NonNull
final List<SplitContainer> mSplitContainers = new ArrayList<>();
+ @NonNull
+ private final Configuration mConfiguration;
+
+ private int mDisplayId;
+
+ private boolean mIsVisible;
+
/**
* TaskFragments that the organizer has requested to be closed. They should be removed when
- * the organizer receives {@link SplitController#onTaskFragmentVanished(TaskFragmentInfo)} event
- * for them.
+ * the organizer receives
+ * {@link SplitController#onTaskFragmentVanished(WindowContainerTransaction, TaskFragmentInfo)}
+ * event for them.
*/
final Set<IBinder> mFinishedContainer = new ArraySet<>();
- TaskContainer(int taskId) {
+ /**
+ * The {@link TaskContainer} constructor
+ *
+ * @param taskId The ID of the Task, which must match {@link Activity#getTaskId()} with
+ * {@code activityInTask}.
+ * @param activityInTask The {@link Activity} in the Task with {@code taskId}. It is used to
+ * initialize the {@link TaskContainer} properties.
+ *
+ */
+ TaskContainer(int taskId, @NonNull Activity activityInTask) {
if (taskId == INVALID_TASK_ID) {
throw new IllegalArgumentException("Invalid Task id");
}
mTaskId = taskId;
+ // Make a copy in case the activity's config is updated, and updates the TaskContainer's
+ // config unexpectedly.
+ mConfiguration = new Configuration(activityInTask.getResources().getConfiguration());
+ mDisplayId = activityInTask.getDisplayId();
+ // Note that it is always called when there's a new Activity is started, which implies
+ // the host task is visible.
+ mIsVisible = true;
}
int getTaskId() {
return mTaskId;
}
+ int getDisplayId() {
+ return mDisplayId;
+ }
+
+ boolean isVisible() {
+ return mIsVisible;
+ }
+
@NonNull
Rect getTaskBounds() {
return mTaskBounds;
@@ -94,13 +126,21 @@
return !mTaskBounds.isEmpty();
}
- void setWindowingMode(int windowingMode) {
- mWindowingMode = windowingMode;
+ @NonNull
+ Configuration getConfiguration() {
+ // Make a copy in case the config is updated unexpectedly.
+ return new Configuration(mConfiguration);
}
- /** Whether the Task windowing mode has been initialized. */
- boolean isWindowingModeInitialized() {
- return mWindowingMode != WINDOWING_MODE_UNDEFINED;
+ @NonNull
+ TaskProperties getTaskProperties() {
+ return new TaskProperties(mDisplayId, mConfiguration);
+ }
+
+ void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
+ mConfiguration.setTo(info.getConfiguration());
+ mDisplayId = info.getDisplayId();
+ mIsVisible = info.isVisibleRequested();
}
/**
@@ -123,13 +163,20 @@
// DecorCaptionView won't work correctly. As a result, have the TaskFragment to be in the
// Task windowing mode if the Task is in multi window.
// TODO we won't need this anymore after we migrate Freeform caption to WM Shell.
- return WindowConfiguration.inMultiWindowMode(mWindowingMode)
- ? mWindowingMode
- : WINDOWING_MODE_MULTI_WINDOW;
+ return isInMultiWindow() ? getWindowingMode() : WINDOWING_MODE_MULTI_WINDOW;
}
boolean isInPictureInPicture() {
- return mWindowingMode == WINDOWING_MODE_PINNED;
+ return getWindowingMode() == WINDOWING_MODE_PINNED;
+ }
+
+ boolean isInMultiWindow() {
+ return WindowConfiguration.inMultiWindowMode(getWindowingMode());
+ }
+
+ @WindowingMode
+ private int getWindowingMode() {
+ return getConfiguration().windowConfiguration.getWindowingMode();
}
/** Whether there is any {@link TaskFragmentContainer} below this Task. */
@@ -173,4 +220,28 @@
int indexOf(@NonNull TaskFragmentContainer child) {
return mContainers.indexOf(child);
}
+
+ /**
+ * A wrapper class which contains the display ID and {@link Configuration} of a
+ * {@link TaskContainer}
+ */
+ static final class TaskProperties {
+ private final int mDisplayId;
+ @NonNull
+ private final Configuration mConfiguration;
+
+ TaskProperties(int displayId, @NonNull Configuration configuration) {
+ mDisplayId = displayId;
+ mConfiguration = configuration;
+ }
+
+ int getDisplayId() {
+ return mDisplayId;
+ }
+
+ @NonNull
+ Configuration getConfiguration() {
+ return mConfiguration;
+ }
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index f24401f..c76f568 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -47,6 +47,7 @@
import androidx.window.util.DataProducer;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -68,6 +69,8 @@
private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
+ private final List<CommonFoldingFeature> mLastReportedFoldingFeatures = new ArrayList<>();
+
private final Map<IBinder, WindowContextConfigListener> mWindowContextConfigListeners =
new ArrayMap<>();
@@ -80,6 +83,11 @@
mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
}
+ /** Registers to listen to {@link CommonFoldingFeature} changes */
+ public void addFoldingStateChangedCallback(Consumer<List<CommonFoldingFeature>> consumer) {
+ mFoldingFeatureProducer.addDataChangedCallback(consumer);
+ }
+
/**
* Adds a listener interested in receiving updates to {@link WindowLayoutInfo}
*
@@ -186,6 +194,8 @@
}
private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
+ mLastReportedFoldingFeatures.clear();
+ mLastReportedFoldingFeatures.addAll(storedFeatures);
for (Context context : getContextsListeningForLayoutChanges()) {
// Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(context);
@@ -207,6 +217,27 @@
}
/**
+ * Gets the current {@link WindowLayoutInfo} computed with passed {@link WindowConfiguration}.
+ *
+ * @return current {@link WindowLayoutInfo} on the default display. Returns
+ * empty {@link WindowLayoutInfo} on secondary displays.
+ */
+ @NonNull
+ public WindowLayoutInfo getCurrentWindowLayoutInfo(int displayId,
+ @NonNull WindowConfiguration windowConfiguration) {
+ return getWindowLayoutInfo(displayId, windowConfiguration, mLastReportedFoldingFeatures);
+ }
+
+ /** @see #getWindowLayoutInfo(Context, List) */
+ private WindowLayoutInfo getWindowLayoutInfo(int displayId,
+ @NonNull WindowConfiguration windowConfiguration,
+ List<CommonFoldingFeature> storedFeatures) {
+ List<DisplayFeature> displayFeatureList = getDisplayFeatures(displayId, windowConfiguration,
+ storedFeatures);
+ return new WindowLayoutInfo(displayFeatureList);
+ }
+
+ /**
* Translate from the {@link CommonFoldingFeature} to
* {@link DisplayFeature} for a given {@link Activity}. If a
* {@link CommonFoldingFeature} is not valid then it will be omitted.
@@ -225,12 +256,23 @@
*/
private List<DisplayFeature> getDisplayFeatures(
@NonNull @UiContext Context context, List<CommonFoldingFeature> storedFeatures) {
- List<DisplayFeature> features = new ArrayList<>();
if (!shouldReportDisplayFeatures(context)) {
+ return Collections.emptyList();
+ }
+ return getDisplayFeatures(context.getDisplayId(),
+ context.getResources().getConfiguration().windowConfiguration,
+ storedFeatures);
+ }
+
+ /** @see #getDisplayFeatures(Context, List) */
+ private List<DisplayFeature> getDisplayFeatures(int displayId,
+ @NonNull WindowConfiguration windowConfiguration,
+ List<CommonFoldingFeature> storedFeatures) {
+ List<DisplayFeature> features = new ArrayList<>();
+ if (displayId != DEFAULT_DISPLAY) {
return features;
}
- int displayId = context.getDisplay().getDisplayId();
for (CommonFoldingFeature baseFeature : storedFeatures) {
Integer state = convertToExtensionState(baseFeature.getState());
if (state == null) {
@@ -238,7 +280,7 @@
}
Rect featureRect = baseFeature.getRect();
rotateRectToDisplayRotation(displayId, featureRect);
- transformToWindowSpaceRect(context, featureRect);
+ transformToWindowSpaceRect(windowConfiguration, featureRect);
if (!isZero(featureRect)) {
// TODO(b/228641877): Remove guarding when fixed.
@@ -263,6 +305,8 @@
windowingMode = ActivityClient.getInstance().getTaskWindowingMode(
context.getActivityToken());
} else {
+ // TODO(b/242674941): use task windowing mode for window context that associates with
+ // activity.
windowingMode = context.getResources().getConfiguration().windowConfiguration
.getWindowingMode();
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
index 31bf963..9e2611f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
@@ -21,6 +21,7 @@
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
+import android.app.WindowConfiguration;
import android.content.Context;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
@@ -89,13 +90,21 @@
/** Transforms rectangle from absolute coordinate space to the window coordinate space. */
public static void transformToWindowSpaceRect(@NonNull @UiContext Context context,
Rect inOutRect) {
- Rect windowRect = getWindowBounds(context);
- if (!Rect.intersects(inOutRect, windowRect)) {
+ transformToWindowSpaceRect(getWindowBounds(context), inOutRect);
+ }
+
+ /** @see ExtensionHelper#transformToWindowSpaceRect(Context, Rect) */
+ public static void transformToWindowSpaceRect(@NonNull WindowConfiguration windowConfiguration,
+ Rect inOutRect) {
+ transformToWindowSpaceRect(windowConfiguration.getBounds(), inOutRect);
+ }
+
+ private static void transformToWindowSpaceRect(@NonNull Rect bounds, @NonNull Rect inOutRect) {
+ if (!inOutRect.intersect(bounds)) {
inOutRect.setEmpty();
return;
}
- inOutRect.intersect(windowRect);
- inOutRect.offset(-windowRect.left, -windowRect.top);
+ inOutRect.offset(-bounds.left, -bounds.top);
}
/**
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
index effc1a3..40f7a27 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -16,9 +16,12 @@
package androidx.window.extensions.embedding;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS;
import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import android.annotation.NonNull;
@@ -26,32 +29,68 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.Pair;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerToken;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType;
+import androidx.window.extensions.layout.DisplayFeature;
+import androidx.window.extensions.layout.FoldingFeature;
+import androidx.window.extensions.layout.WindowLayoutInfo;
+
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
public class EmbeddingTestUtils {
static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);
static final int TASK_ID = 10;
- static final float SPLIT_RATIO = 0.5f;
+ static final SplitType SPLIT_TYPE = SplitType.RatioSplitType.splitEqually();
+ static final SplitAttributes SPLIT_ATTRIBUTES = new SplitAttributes.Builder().build();
+ static final String TEST_TAG = "test";
/** Default finish behavior in Jetpack. */
static final int DEFAULT_FINISH_PRIMARY_WITH_SECONDARY = FINISH_NEVER;
static final int DEFAULT_FINISH_SECONDARY_WITH_PRIMARY = FINISH_ALWAYS;
+ private static final float SPLIT_RATIO = 0.5f;
private EmbeddingTestUtils() {}
/** Gets the bounds of a TaskFragment that is in split. */
static Rect getSplitBounds(boolean isPrimary) {
- final int width = (int) (TASK_BOUNDS.width() * SPLIT_RATIO);
+ return getSplitBounds(isPrimary, false /* shouldSplitHorizontally */);
+ }
+
+ /** Gets the bounds of a TaskFragment that is in split. */
+ static Rect getSplitBounds(boolean isPrimary, boolean shouldSplitHorizontally) {
+ final int dimension = (int) (
+ (shouldSplitHorizontally ? TASK_BOUNDS.height() : TASK_BOUNDS.width())
+ * SPLIT_RATIO);
+ if (shouldSplitHorizontally) {
+ return isPrimary
+ ? new Rect(
+ TASK_BOUNDS.left,
+ TASK_BOUNDS.top,
+ TASK_BOUNDS.right,
+ TASK_BOUNDS.top + dimension)
+ : new Rect(
+ TASK_BOUNDS.left,
+ TASK_BOUNDS.top + dimension,
+ TASK_BOUNDS.right,
+ TASK_BOUNDS.bottom);
+ }
return isPrimary
- ? new Rect(TASK_BOUNDS.left, TASK_BOUNDS.top, TASK_BOUNDS.left + width,
- TASK_BOUNDS.bottom)
+ ? new Rect(
+ TASK_BOUNDS.left,
+ TASK_BOUNDS.top,
+ TASK_BOUNDS.left + dimension,
+ TASK_BOUNDS.bottom)
: new Rect(
- TASK_BOUNDS.left + width, TASK_BOUNDS.top, TASK_BOUNDS.right,
+ TASK_BOUNDS.left + dimension,
+ TASK_BOUNDS.top,
+ TASK_BOUNDS.right,
TASK_BOUNDS.bottom);
}
@@ -69,10 +108,15 @@
activityPair -> false,
targetPair::equals,
w -> true)
- .setSplitRatio(SPLIT_RATIO)
+ .setDefaultSplitAttributes(
+ new SplitAttributes.Builder()
+ .setSplitType(SPLIT_TYPE)
+ .build()
+ )
.setShouldClearTop(clearTop)
.setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
.setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
+ .setTag(TEST_TAG)
.build();
}
@@ -101,10 +145,15 @@
targetPair::equals,
activityIntentPair -> false,
w -> true)
- .setSplitRatio(SPLIT_RATIO)
+ .setDefaultSplitAttributes(
+ new SplitAttributes.Builder()
+ .setSplitType(SPLIT_TYPE)
+ .build()
+ )
.setFinishPrimaryWithSecondary(finishPrimaryWithSecondary)
.setFinishSecondaryWithPrimary(finishSecondaryWithPrimary)
.setShouldClearTop(clearTop)
+ .setTag(TEST_TAG)
.build();
}
@@ -130,4 +179,29 @@
primaryBounds.width() + 1, primaryBounds.height() + 1);
return aInfo;
}
+
+ static TaskContainer createTestTaskContainer() {
+ Resources resources = mock(Resources.class);
+ doReturn(new Configuration()).when(resources).getConfiguration();
+ Activity activity = mock(Activity.class);
+ doReturn(resources).when(activity).getResources();
+ doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
+
+ return new TaskContainer(TASK_ID, activity);
+ }
+
+ static WindowLayoutInfo createWindowLayoutInfo() {
+ final FoldingFeature foldingFeature = new FoldingFeature(
+ new Rect(
+ TASK_BOUNDS.left,
+ TASK_BOUNDS.top + TASK_BOUNDS.height() / 2 - 5,
+ TASK_BOUNDS.right,
+ TASK_BOUNDS.top + TASK_BOUNDS.height() / 2 + 5
+ ),
+ FoldingFeature.TYPE_HINGE,
+ FoldingFeature.STATE_HALF_OPENED);
+ final List<DisplayFeature> displayFeatures = new ArrayList<>();
+ displayFeatures.add(foldingFeature);
+ return new WindowLayoutInfo(displayFeatures);
+ }
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 58a627b..957a248 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -26,12 +27,14 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Point;
+import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentTransaction;
@@ -65,7 +68,10 @@
private WindowContainerTransaction mTransaction;
@Mock
private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback;
+ @Mock
private SplitController mSplitController;
+ @Mock
+ private Handler mHandler;
private JetpackTaskFragmentOrganizer mOrganizer;
@Before
@@ -73,9 +79,8 @@
MockitoAnnotations.initMocks(this);
mOrganizer = new JetpackTaskFragmentOrganizer(Runnable::run, mCallback);
mOrganizer.registerOrganizer();
- mSplitController = new SplitController();
spyOn(mOrganizer);
- spyOn(mSplitController);
+ doReturn(mHandler).when(mSplitController).getHandler();
}
@Test
@@ -113,7 +118,7 @@
@Test
public void testExpandTaskFragment() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
new Intent(), taskContainer, mSplitController);
final TaskFragmentInfo info = createMockInfo(container);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 58870a6..179696a 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -19,6 +19,7 @@
import static android.app.ActivityManager.START_CANCELED;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
@@ -27,15 +28,20 @@
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_RATIO;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_ATTRIBUTES;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TEST_TAG;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -73,14 +79,19 @@
import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.view.WindowInsets;
+import android.view.WindowMetrics;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizer;
+import android.window.TaskFragmentParentInfo;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
+import androidx.window.extensions.layout.WindowLayoutInfo;
import org.junit.Before;
import org.junit.Test;
@@ -116,6 +127,8 @@
private WindowContainerTransaction mTransaction;
@Mock
private Handler mHandler;
+ @Mock
+ private WindowLayoutComponentImpl mWindowLayoutComponent;
private SplitController mSplitController;
private SplitPresenter mSplitPresenter;
@@ -123,7 +136,9 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mSplitController = new SplitController();
+ doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
+ .getCurrentWindowLayoutInfo(anyInt(), any());
+ mSplitController = new SplitController(mWindowLayoutComponent);
mSplitPresenter = mSplitController.mPresenter;
spyOn(mSplitController);
spyOn(mSplitPresenter);
@@ -138,7 +153,7 @@
@Test
public void testGetTopActiveContainer() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
// tf1 has no running activity so is not active.
final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
new Intent(), taskContainer, mSplitController);
@@ -198,6 +213,7 @@
@Test
public void testOnTaskFragmentAppearEmptyTimeout() {
final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
+ doCallRealMethod().when(mSplitController).onTaskFragmentAppearEmptyTimeout(any(), any());
mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf);
verify(mSplitPresenter).cleanupContainer(mTransaction, tf,
@@ -268,6 +284,8 @@
final SplitContainer splitContainer = mock(SplitContainer.class);
doReturn(tf).when(splitContainer).getPrimaryContainer();
doReturn(tf).when(splitContainer).getSecondaryContainer();
+ doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer();
+ doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule();
final List<SplitContainer> splitContainers =
mSplitController.getTaskContainer(TASK_ID).mSplitContainers;
splitContainers.add(splitContainer);
@@ -298,7 +316,7 @@
// Verify if the top active split is updated if both of its containers are not finished.
doReturn(false).when(mSplitController)
- .dismissPlaceholderIfNecessary(mTransaction, splitContainer);
+ .dismissPlaceholderIfNecessary(mTransaction, splitContainer);
mSplitController.updateContainer(mTransaction, tf);
@@ -308,7 +326,7 @@
@Test
public void testOnStartActivityResultError() {
final Intent intent = new Intent();
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
intent, taskContainer, mSplitController);
final SplitController.ActivityStartMonitor monitor =
@@ -608,7 +626,7 @@
assertTrue(result);
verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
- placeholderRule, true /* isPlaceholder */);
+ placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */);
}
@Test
@@ -624,7 +642,7 @@
assertFalse(result);
verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
- anyBoolean());
+ any(), anyBoolean());
}
@Test
@@ -641,7 +659,7 @@
assertTrue(result);
verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
- placeholderRule, true /* isPlaceholder */);
+ placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */);
}
@Test
@@ -656,7 +674,7 @@
assertFalse(result);
verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
- anyBoolean());
+ any(), anyBoolean());
}
@Test
@@ -674,7 +692,7 @@
assertTrue(result);
verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
- placeholderRule, true /* isPlaceholder */);
+ placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */);
}
@Test
@@ -693,14 +711,15 @@
primaryContainer,
mActivity,
secondaryContainer,
- splitRule);
+ splitRule,
+ SPLIT_ATTRIBUTES);
clearInvocations(mSplitController);
final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
- verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any());
+ verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@Test
@@ -720,7 +739,8 @@
primaryContainer,
mActivity,
secondaryContainer,
- splitRule);
+ splitRule,
+ SPLIT_ATTRIBUTES);
final Activity launchedActivity = createMockActivity();
primaryContainer.addPendingAppearedActivity(launchedActivity);
@@ -741,7 +761,7 @@
assertTrue(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
- verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any());
+ verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@Test
@@ -778,7 +798,8 @@
primaryContainer,
mActivity,
secondaryContainer,
- placeholderRule);
+ placeholderRule,
+ SPLIT_ATTRIBUTES);
final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
@@ -1038,15 +1059,16 @@
@Test
public void testOnTransactionReady_taskFragmentParentInfoChanged() {
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
- final Configuration taskConfig = new Configuration();
+ final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(Configuration.EMPTY,
+ DEFAULT_DISPLAY, true);
transaction.addChange(new TaskFragmentTransaction.Change(
TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED)
.setTaskId(TASK_ID)
- .setTaskConfiguration(taskConfig));
+ .setTaskFragmentParentInfo(parentInfo));
mSplitController.onTransactionReady(transaction);
verify(mSplitController).onTaskFragmentParentInfoChanged(any(), eq(TASK_ID),
- eq(taskConfig));
+ eq(parentInfo));
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
anyInt(), anyBoolean());
}
@@ -1088,6 +1110,47 @@
anyInt(), anyBoolean());
}
+ @Test
+ public void testHasSamePresentation() {
+ SplitPairRule splitRule1 = new SplitPairRule.Builder(
+ activityPair -> true,
+ activityIntentPair -> true,
+ windowMetrics -> true)
+ .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
+ .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
+ .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
+ .build();
+ SplitPairRule splitRule2 = new SplitPairRule.Builder(
+ activityPair -> true,
+ activityIntentPair -> true,
+ windowMetrics -> true)
+ .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
+ .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
+ .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
+ .build();
+
+ assertTrue("Rules must have same presentation if tags are null and has same properties.",
+ SplitController.haveSamePresentation(splitRule1, splitRule2,
+ new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
+
+ splitRule2 = new SplitPairRule.Builder(
+ activityPair -> true,
+ activityIntentPair -> true,
+ windowMetrics -> true)
+ .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
+ .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
+ .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
+ .setTag(TEST_TAG)
+ .build();
+
+ assertFalse("Rules must have different presentations if tags are not equal regardless"
+ + "of other properties",
+ SplitController.haveSamePresentation(splitRule1, splitRule2,
+ new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
+
+
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
final Activity activity = mock(Activity.class);
@@ -1097,6 +1160,7 @@
doReturn(activity).when(mSplitController).getActivity(activityToken);
doReturn(TASK_ID).when(activity).getTaskId();
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
+ doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
return activity;
}
@@ -1135,7 +1199,7 @@
private void setupPlaceholderRule(@NonNull Activity primaryActivity) {
final SplitRule placeholderRule = new SplitPlaceholderRule.Builder(PLACEHOLDER_INTENT,
primaryActivity::equals, i -> false, w -> true)
- .setSplitRatio(SPLIT_RATIO)
+ .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
.build();
mSplitController.setEmbeddingRules(Collections.singleton(placeholderRule));
}
@@ -1188,7 +1252,8 @@
primaryContainer,
primaryContainer.getTopNonFinishingActivity(),
secondaryContainer,
- rule);
+ rule,
+ SPLIT_ATTRIBUTES);
// We need to set those in case we are not respecting clear top.
// TODO(b/231845476) we should always respect clearTop.
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index 25f0e25..6dae0a1 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -16,23 +16,28 @@
package androidx.window.extensions.embedding;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_ATTRIBUTES;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createWindowLayoutInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
+import static androidx.window.extensions.embedding.SplitPresenter.EXPAND_CONTAINERS_ATTRIBUTES;
import static androidx.window.extensions.embedding.SplitPresenter.POSITION_END;
import static androidx.window.extensions.embedding.SplitPresenter.POSITION_FILL;
import static androidx.window.extensions.embedding.SplitPresenter.POSITION_START;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPANDED;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_NOT_EXPANDED;
-import static androidx.window.extensions.embedding.SplitPresenter.getBoundsForPosition;
import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
-import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -66,6 +71,8 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
+import androidx.window.extensions.layout.WindowLayoutInfo;
import org.junit.Before;
import org.junit.Test;
@@ -73,6 +80,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+
/**
* Test class for {@link SplitPresenter}.
*
@@ -94,13 +103,17 @@
private TaskFragmentInfo mTaskFragmentInfo;
@Mock
private WindowContainerTransaction mTransaction;
+ @Mock
+ private WindowLayoutComponentImpl mWindowLayoutComponent;
private SplitController mController;
private SplitPresenter mPresenter;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mController = new SplitController();
+ doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
+ .getCurrentWindowLayoutInfo(anyInt(), any());
+ mController = new SplitController(mWindowLayoutComponent);
mPresenter = mController.mPresenter;
spyOn(mController);
spyOn(mPresenter);
@@ -162,59 +175,263 @@
@Test
public void testShouldShowSideBySide() {
- Activity secondaryActivity = createMockActivity();
- final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity);
+ assertTrue(SplitPresenter.shouldShowSplit(SPLIT_ATTRIBUTES));
- assertTrue(shouldShowSideBySide(TASK_BOUNDS, splitRule));
+ final SplitAttributes expandContainers = new SplitAttributes.Builder()
+ .setSplitType(new SplitAttributes.SplitType.ExpandContainersSplitType())
+ .build();
- // Set minDimensions of primary container to larger than primary bounds.
- final Rect primaryBounds = getSplitBounds(true /* isPrimary */);
- Pair<Size, Size> minDimensionsPair = new Pair<>(
- new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null);
-
- assertFalse(shouldShowSideBySide(TASK_BOUNDS, splitRule, minDimensionsPair));
+ assertFalse(SplitPresenter.shouldShowSplit(expandContainers));
}
@Test
- public void testGetBoundsForPosition() {
- Activity secondaryActivity = createMockActivity();
- final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity);
- final Rect primaryBounds = getSplitBounds(true /* isPrimary */);
- final Rect secondaryBounds = getSplitBounds(false /* isPrimary */);
+ public void testGetBoundsForPosition_expandContainers() {
+ final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+ .setSplitType(new SplitAttributes.SplitType.ExpandContainersSplitType())
+ .build();
+
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+ }
+
+ @Test
+ public void testGetBoundsForPosition_splitVertically() {
+ final Rect primaryBounds = getSplitBounds(true /* isPrimary */,
+ false /* splitHorizontally */);
+ final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
+ false /* splitHorizontally */);
+ final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ SplitAttributes splitAttributes = new SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
+ .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
+ .build();
assertEquals("Primary bounds must be reported.",
primaryBounds,
- getBoundsForPosition(POSITION_START, TASK_BOUNDS, splitRule,
- mActivity, null /* miniDimensionsPair */));
+ mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
assertEquals("Secondary bounds must be reported.",
secondaryBounds,
- getBoundsForPosition(POSITION_END, TASK_BOUNDS, splitRule,
- mActivity, null /* miniDimensionsPair */));
+ mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
assertEquals("Task bounds must be reported.",
new Rect(),
- getBoundsForPosition(POSITION_FILL, TASK_BOUNDS, splitRule,
- mActivity, null /* miniDimensionsPair */));
+ mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
- Pair<Size, Size> minDimensionsPair = new Pair<>(
- new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null);
+ splitAttributes = new SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
+ .setLayoutDirection(SplitAttributes.LayoutDirection.RIGHT_TO_LEFT)
+ .build();
- assertEquals("Fullscreen bounds must be reported because of min dimensions.",
+ assertEquals("Secondary bounds must be reported.",
+ secondaryBounds,
+ mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+ assertEquals("Primary bounds must be reported.",
+ primaryBounds,
+ mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ assertEquals("Task bounds must be reported.",
new Rect(),
- getBoundsForPosition(POSITION_START, TASK_BOUNDS,
- splitRule, mActivity, minDimensionsPair));
+ mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+
+ splitAttributes = new SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
+ .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
+ .build();
+ // Layout direction should follow screen layout for SplitAttributes.LayoutDirection.LOCALE.
+ taskProperties.getConfiguration().screenLayout |= Configuration.SCREENLAYOUT_LAYOUTDIR_RTL;
+
+ assertEquals("Secondary bounds must be reported.",
+ secondaryBounds,
+ mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+ assertEquals("Primary bounds must be reported.",
+ primaryBounds,
+ mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+ }
+
+ @Test
+ public void testGetBoundsForPosition_splitHorizontally() {
+ final Rect primaryBounds = getSplitBounds(true /* isPrimary */,
+ true /* splitHorizontally */);
+ final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
+ true /* splitHorizontally */);
+ final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ SplitAttributes splitAttributes = new SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
+ .setLayoutDirection(SplitAttributes.LayoutDirection.TOP_TO_BOTTOM)
+ .build();
+
+ assertEquals("Primary bounds must be reported.",
+ primaryBounds,
+ mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+ assertEquals("Secondary bounds must be reported.",
+ secondaryBounds,
+ mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+
+ splitAttributes = new SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
+ .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP)
+ .build();
+
+ assertEquals("Secondary bounds must be reported.",
+ secondaryBounds,
+ mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+ assertEquals("Primary bounds must be reported.",
+ primaryBounds,
+ mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+ }
+
+ @Test
+ public void testGetBoundsForPosition_useHingeFallback() {
+ final Rect primaryBounds = getSplitBounds(true /* isPrimary */,
+ false /* splitHorizontally */);
+ final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
+ false /* splitHorizontally */);
+ final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+ .setSplitType(new SplitAttributes.SplitType.HingeSplitType(
+ SplitAttributes.SplitType.RatioSplitType.splitEqually()
+ )).setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
+ .build();
+
+ // There's no hinge on the device. Use fallback SplitType.
+ doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
+ .getCurrentWindowLayoutInfo(anyInt(), any());
+
+ assertEquals("PrimaryBounds must be reported.",
+ primaryBounds,
+ mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+ assertEquals("SecondaryBounds must be reported.",
+ secondaryBounds,
+ mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+
+ // Hinge is reported, but the host task is in multi-window mode. Still use fallback
+ // splitType.
+ doReturn(createWindowLayoutInfo()).when(mWindowLayoutComponent)
+ .getCurrentWindowLayoutInfo(anyInt(), any());
+ taskProperties.getConfiguration().windowConfiguration
+ .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+
+ assertEquals("PrimaryBounds must be reported.",
+ primaryBounds,
+ mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+ assertEquals("SecondaryBounds must be reported.",
+ secondaryBounds,
+ mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+
+ // Hinge is reported, and the host task is in fullscreen, but layout direction doesn't match
+ // folding area orientation. Still use fallback splitType.
+ doReturn(createWindowLayoutInfo()).when(mWindowLayoutComponent)
+ .getCurrentWindowLayoutInfo(anyInt(), any());
+ taskProperties.getConfiguration().windowConfiguration
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ assertEquals("PrimaryBounds must be reported.",
+ primaryBounds,
+ mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+ assertEquals("SecondaryBounds must be reported.",
+ secondaryBounds,
+ mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+ }
+
+ @Test
+ public void testGetBoundsForPosition_fallbackToExpandContainers() {
+ final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+ .setSplitType(new SplitAttributes.SplitType.HingeSplitType(
+ new SplitAttributes.SplitType.ExpandContainersSplitType()
+ )).setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
+ .build();
+
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+ }
+
+ @Test
+ public void testGetBoundsForPosition_useHingeSplitType() {
+ final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+ .setSplitType(new SplitAttributes.SplitType.HingeSplitType(
+ new SplitAttributes.SplitType.ExpandContainersSplitType()
+ )).setLayoutDirection(SplitAttributes.LayoutDirection.TOP_TO_BOTTOM)
+ .build();
+ final WindowLayoutInfo windowLayoutInfo = createWindowLayoutInfo();
+ doReturn(windowLayoutInfo).when(mWindowLayoutComponent)
+ .getCurrentWindowLayoutInfo(anyInt(), any());
+ final Rect hingeBounds = windowLayoutInfo.getDisplayFeatures().get(0).getBounds();
+ final Rect primaryBounds = new Rect(
+ TASK_BOUNDS.left,
+ TASK_BOUNDS.top,
+ TASK_BOUNDS.right,
+ hingeBounds.top
+ );
+ final Rect secondaryBounds = new Rect(
+ TASK_BOUNDS.left,
+ hingeBounds.bottom,
+ TASK_BOUNDS.right,
+ TASK_BOUNDS.bottom
+ );
+
+ assertEquals("PrimaryBounds must be reported.",
+ primaryBounds,
+ mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+ assertEquals("SecondaryBounds must be reported.",
+ secondaryBounds,
+ mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
}
@Test
public void testExpandSplitContainerIfNeeded() {
- SplitContainer splitContainer = mock(SplitContainer.class);
Activity secondaryActivity = createMockActivity();
SplitRule splitRule = createSplitRule(mActivity, secondaryActivity);
TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID);
TaskFragmentContainer secondaryTf = mController.newContainer(secondaryActivity, TASK_ID);
- doReturn(splitRule).when(splitContainer).getSplitRule();
- doReturn(primaryTf).when(splitContainer).getPrimaryContainer();
- doReturn(secondaryTf).when(splitContainer).getSecondaryContainer();
+ SplitContainer splitContainer = new SplitContainer(primaryTf, secondaryActivity,
+ secondaryTf, splitRule, SPLIT_ATTRIBUTES);
assertThrows(IllegalArgumentException.class, () ->
mPresenter.expandSplitContainerIfNeeded(mTransaction, splitContainer, mActivity,
@@ -224,11 +441,13 @@
splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
verify(mPresenter, never()).expandTaskFragment(any(), any());
+ splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
doReturn(createActivityInfoWithMinDimensions()).when(secondaryActivity).getActivityInfo();
assertEquals(RESULT_EXPAND_FAILED_NO_TF_INFO, mPresenter.expandSplitContainerIfNeeded(
mTransaction, splitContainer, mActivity, secondaryActivity,
null /* secondaryIntent */));
+ splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
primaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(primaryTf, mActivity));
secondaryTf.setInfo(mTransaction,
createMockTaskFragmentInfo(secondaryTf, secondaryActivity));
@@ -238,6 +457,7 @@
verify(mPresenter).expandTaskFragment(mTransaction, primaryTf.getTaskFragmentToken());
verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken());
+ splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
clearInvocations(mPresenter);
assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
@@ -256,6 +476,7 @@
final SplitPairRule rule = new SplitPairRule.Builder(pair ->
pair.first == mActivity && pair.second == secondaryActivity, pair -> false,
metrics -> true)
+ .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
.setShouldClearTop(false)
.build();
@@ -268,6 +489,49 @@
assertTrue(secondaryTf.isAbove(primaryTf));
}
+ @Test
+ public void testComputeSplitAttributes() {
+ final SplitPairRule splitPairRule = new SplitPairRule.Builder(
+ activityPair -> true,
+ activityIntentPair -> true,
+ windowMetrics -> windowMetrics.getBounds().equals(TASK_BOUNDS))
+ .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
+ .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
+ .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
+ .build();
+ final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+
+ assertEquals(SPLIT_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties,
+ splitPairRule, null /* minDimensionsPair */));
+
+ final Pair<Size, Size> minDimensionsPair = new Pair<>(
+ new Size(TASK_BOUNDS.width(), TASK_BOUNDS.height()), null);
+
+ assertEquals(EXPAND_CONTAINERS_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties,
+ splitPairRule, minDimensionsPair));
+
+ taskProperties.getConfiguration().windowConfiguration.setBounds(new Rect(
+ TASK_BOUNDS.left + 1, TASK_BOUNDS.top + 1, TASK_BOUNDS.right + 1,
+ TASK_BOUNDS.bottom + 1));
+
+ assertEquals(EXPAND_CONTAINERS_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties,
+ splitPairRule, null /* minDimensionsPair */));
+
+ final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+ .setSplitType(
+ new SplitAttributes.SplitType.HingeSplitType(
+ SplitAttributes.SplitType.RatioSplitType.splitEqually()
+ )
+ ).build();
+
+ mController.setSplitAttributesCalculator(params -> {
+ return splitAttributes;
+ });
+
+ assertEquals(splitAttributes, mPresenter.computeSplitAttributes(taskProperties,
+ splitPairRule, null /* minDimensionsPair */));
+ }
+
private Activity createMockActivity() {
final Activity activity = mock(Activity.class);
final Configuration activityConfig = new Configuration();
@@ -279,4 +543,10 @@
doReturn(mock(IBinder.class)).when(activity).getActivityToken();
return activity;
}
+
+ private static TaskContainer.TaskProperties getTaskProperty() {
+ final Configuration configuration = new Configuration();
+ configuration.windowConfiguration.setBounds(TASK_BOUNDS);
+ return new TaskContainer.TaskProperties(DEFAULT_DISPLAY, configuration);
+ }
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index dd67e48..af9c6ba 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -21,9 +21,10 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -34,8 +35,10 @@
import android.app.Activity;
import android.content.Intent;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.window.TaskFragmentParentInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -66,7 +69,7 @@
@Test
public void testIsTaskBoundsInitialized() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
assertFalse(taskContainer.isTaskBoundsInitialized());
@@ -77,7 +80,7 @@
@Test
public void testSetTaskBounds() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
assertFalse(taskContainer.setTaskBounds(new Rect()));
@@ -87,30 +90,24 @@
}
@Test
- public void testIsWindowingModeInitialized() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
-
- assertFalse(taskContainer.isWindowingModeInitialized());
-
- taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-
- assertTrue(taskContainer.isWindowingModeInitialized());
- }
-
- @Test
public void testGetWindowingModeForSplitTaskFragment() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
final Rect splitBounds = new Rect(0, 0, 500, 1000);
+ final Configuration configuration = new Configuration();
assertEquals(WINDOWING_MODE_MULTI_WINDOW,
taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
- taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
+ DEFAULT_DISPLAY, true /* visible */));
assertEquals(WINDOWING_MODE_MULTI_WINDOW,
taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
- taskContainer.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
+ DEFAULT_DISPLAY, true /* visible */));
assertEquals(WINDOWING_MODE_FREEFORM,
taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
@@ -123,22 +120,27 @@
@Test
public void testIsInPictureInPicture() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final Configuration configuration = new Configuration();
assertFalse(taskContainer.isInPictureInPicture());
- taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
+ DEFAULT_DISPLAY, true /* visible */));
assertFalse(taskContainer.isInPictureInPicture());
- taskContainer.setWindowingMode(WINDOWING_MODE_PINNED);
+ configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED);
+ taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
+ DEFAULT_DISPLAY, true /* visible */));
assertTrue(taskContainer.isInPictureInPicture());
}
@Test
public void testIsEmpty() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
assertTrue(taskContainer.isEmpty());
@@ -155,7 +157,7 @@
@Test
public void testGetTopTaskFragmentContainer() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
assertNull(taskContainer.getTopTaskFragmentContainer());
final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */,
@@ -169,7 +171,7 @@
@Test
public void testGetTopNonFinishingActivity() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
assertNull(taskContainer.getTopNonFinishingActivity());
final TaskFragmentContainer tf0 = mock(TaskFragmentContainer.class);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 082774e..73428a2 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -16,8 +16,8 @@
package androidx.window.extensions.embedding;
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -90,7 +90,7 @@
@Test
public void testNewContainer() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
// One of the activity and the intent must be non-null
assertThrows(IllegalArgumentException.class,
@@ -103,7 +103,7 @@
@Test
public void testFinish() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(mActivity,
null /* pendingAppearedIntent */, taskContainer, mController);
doReturn(container).when(mController).getContainerWithActivity(mActivity);
@@ -136,7 +136,7 @@
@Test
public void testFinish_notFinishActivityThatIsReparenting() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity,
null /* pendingAppearedIntent */, taskContainer, mController);
final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity);
@@ -157,7 +157,7 @@
@Test
public void testSetInfo() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
// Pending activity should be cleared when it has appeared on server side.
final TaskFragmentContainer pendingActivityContainer = new TaskFragmentContainer(mActivity,
null /* pendingAppearedIntent */, taskContainer, mController);
@@ -185,7 +185,7 @@
@Test
public void testIsWaitingActivityAppear() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController);
@@ -207,7 +207,7 @@
@Test
public void testAppearEmptyTimeout() {
doNothing().when(mController).onTaskFragmentAppearEmptyTimeout(any(), any());
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController);
@@ -247,7 +247,7 @@
@Test
public void testCollectNonFinishingActivities() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController);
List<Activity> activities = container.collectNonFinishingActivities();
@@ -275,7 +275,7 @@
@Test
public void testAddPendingActivity() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController);
container.addPendingAppearedActivity(mActivity);
@@ -289,7 +289,7 @@
@Test
public void testIsAbove() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container0 = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController);
final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */,
@@ -301,7 +301,7 @@
@Test
public void testGetBottomMostActivity() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController);
container.addPendingAppearedActivity(mActivity);
@@ -318,7 +318,7 @@
@Test
public void testOnActivityDestroyed() {
- final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController);
container.addPendingAppearedActivity(mActivity);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index 9230c22..ca977ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -179,6 +179,14 @@
}
/**
+ * Returns the {@link DisplayAreaInfo} of the {@link DisplayAreaInfo#displayId}.
+ */
+ @Nullable
+ public DisplayAreaInfo getDisplayAreaInfo(int displayId) {
+ return mDisplayAreasInfo.get(displayId);
+ }
+
+ /**
* Applies the {@link DisplayAreaInfo} to the {@link DisplayAreaContext} specified by
* {@link DisplayAreaInfo#displayId}.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index d88cc00..d150261 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -18,6 +18,7 @@
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import android.animation.Animator;
import android.animation.ValueAnimator;
@@ -129,11 +130,19 @@
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters(
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
+ boolean isChangeTransition = false;
for (TransitionInfo.Change change : info.getChanges()) {
- if (change.getMode() == TRANSIT_CHANGE
- && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
- return createChangeAnimationAdapters(info, startTransaction);
+ if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) {
+ // Skip the animation if the windows are behind an app starting window.
+ return new ArrayList<>();
}
+ if (!isChangeTransition && change.getMode() == TRANSIT_CHANGE
+ && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
+ isChangeTransition = true;
+ }
+ }
+ if (isChangeTransition) {
+ return createChangeAnimationAdapters(info, startTransaction);
}
if (Transitions.isClosingType(info.getType())) {
return createCloseAnimationAdapters(info);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 27d3e35..35e88e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -27,7 +27,6 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.IconProvider;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
@@ -599,13 +598,13 @@
static Optional<DesktopModeController> provideDesktopModeController(
Context context, ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
- RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
@ShellMainThread Handler mainHandler,
Transitions transitions
) {
if (DesktopMode.IS_SUPPORTED) {
return Optional.of(new DesktopModeController(context, shellInit, shellTaskOrganizer,
- rootDisplayAreaOrganizer, mainHandler, transitions));
+ rootTaskDisplayAreaOrganizer, mainHandler, transitions));
} else {
return Optional.empty();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index c07ce10..6e44d58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -22,19 +22,21 @@
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+import android.app.WindowConfiguration;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
+import android.window.DisplayAreaInfo;
import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
@@ -47,18 +49,18 @@
private final Context mContext;
private final ShellTaskOrganizer mShellTaskOrganizer;
- private final RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
+ private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final SettingsObserver mSettingsObserver;
private final Transitions mTransitions;
public DesktopModeController(Context context, ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
- RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
@ShellMainThread Handler mainHandler,
Transitions transitions) {
mContext = context;
mShellTaskOrganizer = shellTaskOrganizer;
- mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
+ mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mSettingsObserver = new SettingsObserver(mContext, mainHandler);
mTransitions = transitions;
shellInit.addInitCallback(this::onInit, this);
@@ -92,15 +94,32 @@
wct.merge(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(displayId),
true /* transfer */);
}
- wct.merge(mRootDisplayAreaOrganizer.prepareWindowingModeChange(displayId,
- targetWindowingMode), true /* transfer */);
+ prepareWindowingModeChange(wct, displayId, targetWindowingMode);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
} else {
- mRootDisplayAreaOrganizer.applyTransaction(wct);
+ mRootTaskDisplayAreaOrganizer.applyTransaction(wct);
}
}
+ private void prepareWindowingModeChange(WindowContainerTransaction wct,
+ int displayId, @WindowConfiguration.WindowingMode int windowingMode) {
+ DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer
+ .getDisplayAreaInfo(displayId);
+ if (displayAreaInfo == null) {
+ ProtoLog.e(WM_SHELL_DESKTOP_MODE,
+ "unable to update windowing mode for display %d display not found", displayId);
+ return;
+ }
+
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
+ displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
+ windowingMode);
+
+ wct.setWindowingMode(displayAreaInfo.token, windowingMode);
+ }
+
/**
* A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 3758471..991f136 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -133,6 +133,20 @@
@Retention(RetentionPolicy.SOURCE)
@interface ExitReason{}
+ public static final int ENTER_REASON_UNKNOWN = 0;
+ public static final int ENTER_REASON_MULTI_INSTANCE = 1;
+ public static final int ENTER_REASON_DRAG = 2;
+ public static final int ENTER_REASON_LAUNCHER = 3;
+ /** Acts as a mapping to the actual EnterReasons as defined in the logging proto */
+ @IntDef(value = {
+ ENTER_REASON_MULTI_INSTANCE,
+ ENTER_REASON_DRAG,
+ ENTER_REASON_LAUNCHER,
+ ENTER_REASON_UNKNOWN
+ })
+ public @interface SplitEnterReason {
+ }
+
private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
private final ShellTaskOrganizer mTaskOrganizer;
@@ -394,7 +408,7 @@
*/
public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
@Nullable Bundle options, UserHandle user, @NonNull InstanceId instanceId) {
- mStageCoordinator.getLogger().enterRequested(instanceId);
+ mStageCoordinator.getLogger().enterRequested(instanceId, ENTER_REASON_LAUNCHER);
startShortcut(packageName, shortcutId, position, options, user);
}
@@ -442,7 +456,7 @@
*/
public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options, @NonNull InstanceId instanceId) {
- mStageCoordinator.getLogger().enterRequested(instanceId);
+ mStageCoordinator.getLogger().enterRequested(instanceId, ENTER_REASON_LAUNCHER);
startIntent(intent, fillInIntent, position, options);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
index 626ccb1..2dc4a04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -17,6 +17,8 @@
package com.android.wm.shell.splitscreen;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__LAUNCHER;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__MULTI_INSTANCE;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__UNKNOWN_ENTER;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
@@ -28,6 +30,10 @@
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_DRAG;
+import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_LAUNCHER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
+import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_UNKNOWN;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
@@ -38,6 +44,7 @@
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN;
+import android.annotation.Nullable;
import android.util.Slog;
import com.android.internal.logging.InstanceId;
@@ -59,7 +66,7 @@
// Drag info
private @SplitPosition int mDragEnterPosition;
- private InstanceId mEnterSessionId;
+ private @Nullable InstanceId mEnterSessionId;
// For deduping async events
private int mLastMainStagePosition = -1;
@@ -67,6 +74,7 @@
private int mLastSideStagePosition = -1;
private int mLastSideStageUid = -1;
private float mLastSplitRatio = -1f;
+ private @SplitScreenController.SplitEnterReason int mEnterReason = ENTER_REASON_UNKNOWN;
public SplitscreenEventLogger() {
mIdSequence = new InstanceIdSequence(Integer.MAX_VALUE);
@@ -79,20 +87,35 @@
return mLoggerSessionId != null;
}
+ public boolean isEnterRequestedByDrag() {
+ return mEnterReason == ENTER_REASON_DRAG;
+ }
+
/**
* May be called before logEnter() to indicate that the session was started from a drag.
*/
public void enterRequestedByDrag(@SplitPosition int position, InstanceId enterSessionId) {
mDragEnterPosition = position;
- enterRequested(enterSessionId);
+ enterRequested(enterSessionId, ENTER_REASON_DRAG);
}
/**
* May be called before logEnter() to indicate that the session was started from launcher.
* This specifically is for all the scenarios where split started without a drag interaction
*/
- public void enterRequested(InstanceId enterSessionId) {
+ public void enterRequested(@Nullable InstanceId enterSessionId,
+ @SplitScreenController.SplitEnterReason int enterReason) {
mEnterSessionId = enterSessionId;
+ mEnterReason = enterReason;
+ }
+
+ /**
+ * @return if an enterSessionId has been set via either
+ * {@link #enterRequested(InstanceId, int)} or
+ * {@link #enterRequestedByDrag(int, InstanceId)}
+ */
+ public boolean hasValidEnterSessionId() {
+ return mEnterSessionId != null;
}
/**
@@ -103,9 +126,7 @@
@SplitPosition int sideStagePosition, int sideStageUid,
boolean isLandscape) {
mLoggerSessionId = mIdSequence.newInstanceId();
- int enterReason = mDragEnterPosition != SPLIT_POSITION_UNDEFINED
- ? getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape)
- : SPLITSCREEN_UICHANGED__ENTER_REASON__LAUNCHER;
+ int enterReason = getLoggerEnterReason(isLandscape);
updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
mainStageUid);
updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
@@ -124,6 +145,20 @@
mLoggerSessionId.getId());
}
+ private int getLoggerEnterReason(boolean isLandscape) {
+ switch (mEnterReason) {
+ case ENTER_REASON_MULTI_INSTANCE:
+ return SPLITSCREEN_UICHANGED__ENTER_REASON__MULTI_INSTANCE;
+ case ENTER_REASON_LAUNCHER:
+ return SPLITSCREEN_UICHANGED__ENTER_REASON__LAUNCHER;
+ case ENTER_REASON_DRAG:
+ return getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape);
+ case ENTER_REASON_UNKNOWN:
+ default:
+ return SPLITSCREEN_UICHANGED__ENTER_REASON__UNKNOWN_ENTER;
+ }
+ }
+
/**
* Returns the framework logging constant given a splitscreen exit reason.
*/
@@ -189,6 +224,7 @@
mLastMainStageUid = -1;
mLastSideStagePosition = -1;
mLastSideStageUid = -1;
+ mEnterReason = ENTER_REASON_UNKNOWN;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 25793ed..c17f822 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -18,6 +18,8 @@
import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED;
+import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
@@ -44,6 +46,8 @@
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_LAUNCHER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
@@ -501,6 +505,12 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
+ // If split still not active, apply windows bounds first to avoid surface reset to
+ // wrong pos by SurfaceAnimator from wms.
+ if (!mMainStage.isActive() && mLogger.isEnterRequestedByDrag()) {
+ updateWindowBounds(mSplitLayout, wct);
+ }
+
wct.sendPendingIntent(intent, fillInIntent, options);
mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
}
@@ -669,7 +679,7 @@
private void setEnterInstanceId(InstanceId instanceId) {
if (instanceId != null) {
- mLogger.enterRequested(instanceId);
+ mLogger.enterRequested(instanceId, ENTER_REASON_LAUNCHER);
}
}
@@ -1107,6 +1117,10 @@
private void addActivityOptions(Bundle opts, StageTaskListener stage) {
opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
+ // Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split
+ // will be canceled.
+ opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
+ opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
}
void updateActivityOptions(Bundle opts, @SplitPosition int position) {
@@ -1453,18 +1467,27 @@
}
}
} else if (isSideStage && hasChildren && !mMainStage.isActive()) {
- // TODO (b/238697912) : Add the validation to prevent entering non-recovered status
- onSplitScreenEnter();
final WindowContainerTransaction wct = new WindowContainerTransaction();
mSplitLayout.init();
- mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
- mMainStage.activate(wct, true /* includingTopTask */);
- updateWindowBounds(mSplitLayout, wct);
- wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ if (mLogger.isEnterRequestedByDrag()) {
+ prepareEnterSplitScreen(wct);
+ } else {
+ // TODO (b/238697912) : Add the validation to prevent entering non-recovered status
+ onSplitScreenEnter();
+ mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+ mMainStage.activate(wct, true /* includingTopTask */);
+ updateWindowBounds(mSplitLayout, wct);
+ wct.reorder(mRootTaskInfo.token, true);
+ wct.setForceTranslucent(mRootTaskInfo.token, false);
+ }
+
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
- mSplitLayout.flingDividerToCenter();
+ if (mLogger.isEnterRequestedByDrag()) {
+ updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+ } else {
+ mSplitLayout.flingDividerToCenter();
+ }
});
}
if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
@@ -1472,6 +1495,9 @@
updateRecentTasksSplitPair();
if (!mLogger.hasStartedSession()) {
+ if (!mLogger.hasValidEnterSessionId()) {
+ mLogger.enterRequested(null /*enterSessionId*/, ENTER_REASON_MULTI_INSTANCE);
+ }
mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
getMainStagePosition(), mMainStage.getTopChildTaskUid(),
getSideStagePosition(), mSideStage.getTopChildTaskUid(),
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 3ca5b9c..d6adaa7 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -48,6 +48,6 @@
"wm-flicker-common-assertions",
"wm-flicker-common-app-helpers",
"platform-test-annotations",
- "wmshell-flicker-test-components",
+ "flickertestapplib",
],
}
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 574a9f4..1284c41 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -19,7 +19,7 @@
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
<option name="test-file-name" value="WMShellFlickerTests.apk"/>
- <option name="test-file-name" value="WMShellFlickerTestApp.apk" />
+ <option name="test-file-name" value="FlickerTestApp.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="package" value="com.android.wm.shell.flicker"/>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt
new file mode 100644
index 0000000..c045325
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.app.Instrumentation
+import android.content.Context
+import android.provider.Settings
+import android.util.Log
+import com.android.compatibility.common.util.SystemUtil
+import java.io.IOException
+
+object MultiWindowUtils {
+ private fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
+ try {
+ SystemUtil.runShellCommand(instrumentation, cmd)
+ } catch (e: IOException) {
+ Log.e(MultiWindowUtils::class.simpleName, "executeShellCommand error! $e")
+ }
+ }
+
+ fun getDevEnableNonResizableMultiWindow(context: Context): Int =
+ Settings.Global.getInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
+
+ fun setDevEnableNonResizableMultiWindow(context: Context, configValue: Int) =
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, configValue)
+
+ fun setSupportsNonResizableMultiWindow(instrumentation: Instrumentation, configValue: Int) =
+ executeShellCommand(
+ instrumentation,
+ createConfigSupportsNonResizableMultiWindowCommand(configValue))
+
+ fun resetMultiWindowConfig(instrumentation: Instrumentation) =
+ executeShellCommand(instrumentation, resetMultiWindowConfigCommand)
+
+ private fun createConfigSupportsNonResizableMultiWindowCommand(configValue: Int): String =
+ "wm set-multi-window-config --supportsNonResizable $configValue"
+
+ private const val resetMultiWindowConfigCommand: String = "wm reset-multi-window-config"
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index 1390334..cbe085b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -19,6 +19,7 @@
import android.app.INotificationManager
import android.app.NotificationManager
import android.content.Context
+import android.content.pm.PackageManager
import android.os.ServiceManager
import android.view.Surface
import androidx.test.uiautomator.By
@@ -28,9 +29,9 @@
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.LaunchBubbleHelper
import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
import com.android.wm.shell.flicker.BaseTest
-import com.android.wm.shell.flicker.helpers.LaunchBubbleHelper
import org.junit.runners.Parameterized
/**
@@ -47,7 +48,7 @@
ServiceManager.getService(Context.NOTIFICATION_SERVICE))
private val uid = context.packageManager.getApplicationInfo(
- testApp.`package`, 0).uid
+ testApp.`package`, PackageManager.ApplicationInfoFlags.of(0)).uid
@JvmOverloads
protected open fun buildTransition(
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
deleted file mode 100644
index 01ba990..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.helpers
-
-import android.app.Instrumentation
-import android.content.pm.PackageManager.FEATURE_LEANBACK
-import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY
-import android.support.test.launcherhelper.LauncherStrategyFactory
-import android.util.Log
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.UiObject2
-import androidx.test.uiautomator.Until
-import com.android.compatibility.common.util.SystemUtil
-import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.traces.common.IComponentNameMatcher
-import java.io.IOException
-
-abstract class BaseAppHelper(
- instrumentation: Instrumentation,
- launcherName: String,
- component: IComponentNameMatcher
-) : StandardAppHelper(
- instrumentation,
- launcherName,
- component,
- LauncherStrategyFactory.getInstance(instrumentation).launcherStrategy
-) {
- private val appSelector = By.pkg(`package`).depth(0)
-
- protected val isTelevision: Boolean
- get() = context.packageManager.run {
- hasSystemFeature(FEATURE_LEANBACK) || hasSystemFeature(FEATURE_LEANBACK_ONLY)
- }
-
- val ui: UiObject2?
- get() = uiDevice.findObject(appSelector)
-
- fun waitUntilClosed(): Boolean {
- return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS)
- }
-
- companion object {
- private const val APP_CLOSE_WAIT_TIME_MS = 3_000L
-
- fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
- try {
- SystemUtil.runShellCommand(instrumentation, cmd)
- } catch (e: IOException) {
- Log.e("BaseAppHelper", "executeShellCommand error! $e")
- }
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt
deleted file mode 100644
index 471e010..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.helpers
-
-import android.app.Instrumentation
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.wm.shell.flicker.testapp.Components
-
-class FixedAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
- instrumentation,
- Components.FixedActivity.LABEL,
- Components.FixedActivity.COMPONENT.toFlickerComponent()
-)
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
deleted file mode 100644
index 2e690de..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.helpers
-
-import android.app.Instrumentation
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.testapp.Components
-
-open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
- instrumentation,
- Components.ImeActivity.LABEL,
- Components.ImeActivity.COMPONENT.toFlickerComponent()
-) {
- /**
- * Opens the IME and wait for it to be displayed
- *
- * @param wmHelper Helper used to wait for WindowManager states
- */
- open fun openIME(wmHelper: WindowManagerStateHelper) {
- if (!isTelevision) {
- val editText = uiDevice.wait(
- Until.findObject(By.res(getPackage(), "plain_text_input")),
- FIND_TIMEOUT)
-
- require(editText != null) {
- "Text field not found, this usually happens when the device " +
- "was left in an unknown state (e.g. in split screen)"
- }
- editText.click()
- wmHelper.StateSyncBuilder()
- .withImeShown()
- .waitForAndVerify()
- } else {
- // If we do the same thing as above - editText.click() - on TV, that's going to force TV
- // into the touch mode. We really don't want that.
- launchViaIntent(action = Components.ImeActivity.ACTION_OPEN_IME)
- }
- }
-
- /**
- * Opens the IME and wait for it to be gone
- *
- * @param wmHelper Helper used to wait for WindowManager states
- */
- open fun closeIME(wmHelper: WindowManagerStateHelper) {
- if (!isTelevision) {
- uiDevice.pressBack()
- // Using only the AccessibilityInfo it is not possible to identify if the IME is active
- wmHelper.StateSyncBuilder()
- .withImeGone()
- .waitForAndVerify()
- } else {
- // While pressing the back button should close the IME on TV as well, it may also lead
- // to the app closing. So let's instead just ask the app to close the IME.
- launchViaIntent(action = Components.ImeActivity.ACTION_CLOSE_IME)
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt
deleted file mode 100644
index 1b8a44b..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2021 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.helpers
-
-import android.app.Instrumentation
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.wm.shell.flicker.testapp.Components
-
-class LaunchBubbleHelper(instrumentation: Instrumentation) : BaseAppHelper(
- instrumentation,
- Components.LaunchBubbleActivity.LABEL,
- Components.LaunchBubbleActivity.COMPONENT.toFlickerComponent()
-) {
-
- companion object {
- const val TIMEOUT_MS = 3_000L
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
deleted file mode 100644
index 52e5d7e..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.helpers
-
-import android.app.Instrumentation
-import android.graphics.Point
-import android.os.SystemClock
-import android.view.InputDevice
-import android.view.MotionEvent
-import android.view.ViewConfiguration
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
-import androidx.test.uiautomator.UiDevice
-import androidx.test.uiautomator.Until
-import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.traces.common.IComponentMatcher
-import com.android.server.wm.traces.common.IComponentNameMatcher
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.SPLIT_DECOR_MANAGER
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.testapp.Components
-
-class SplitScreenHelper(
- instrumentation: Instrumentation,
- activityLabel: String,
- componentInfo: IComponentNameMatcher
-) : BaseAppHelper(instrumentation, activityLabel, componentInfo) {
-
- companion object {
- const val TIMEOUT_MS = 3_000L
- const val DRAG_DURATION_MS = 1_000L
- const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
- const val DIVIDER_BAR = "docked_divider_handle"
- const val GESTURE_STEP_MS = 16L
- const val LONG_PRESS_TIME_MS = 100L
-
- private val notificationScrollerSelector: BySelector
- get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
- private val notificationContentSelector: BySelector
- get() = By.text("Notification content")
- private val dividerBarSelector: BySelector
- get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
-
- fun getPrimary(instrumentation: Instrumentation): SplitScreenHelper =
- SplitScreenHelper(
- instrumentation,
- Components.SplitScreenActivity.LABEL,
- Components.SplitScreenActivity.COMPONENT.toFlickerComponent()
- )
-
- fun getSecondary(instrumentation: Instrumentation): SplitScreenHelper =
- SplitScreenHelper(
- instrumentation,
- Components.SplitScreenSecondaryActivity.LABEL,
- Components.SplitScreenSecondaryActivity.COMPONENT.toFlickerComponent()
- )
-
- fun getNonResizeable(instrumentation: Instrumentation): SplitScreenHelper =
- SplitScreenHelper(
- instrumentation,
- Components.NonResizeableActivity.LABEL,
- Components.NonResizeableActivity.COMPONENT.toFlickerComponent()
- )
-
- fun getSendNotification(instrumentation: Instrumentation): SplitScreenHelper =
- SplitScreenHelper(
- instrumentation,
- Components.SendNotificationActivity.LABEL,
- Components.SendNotificationActivity.COMPONENT.toFlickerComponent()
- )
-
- fun getIme(instrumentation: Instrumentation): SplitScreenHelper =
- SplitScreenHelper(
- instrumentation,
- Components.ImeActivity.LABEL,
- Components.ImeActivity.COMPONENT.toFlickerComponent()
- )
-
- fun waitForSplitComplete(
- wmHelper: WindowManagerStateHelper,
- primaryApp: IComponentMatcher,
- secondaryApp: IComponentMatcher,
- ) {
- wmHelper.StateSyncBuilder()
- .withWindowSurfaceAppeared(primaryApp)
- .withWindowSurfaceAppeared(secondaryApp)
- .withSplitDividerVisible()
- .waitForAndVerify()
- }
-
- fun enterSplit(
- wmHelper: WindowManagerStateHelper,
- tapl: LauncherInstrumentation,
- primaryApp: SplitScreenHelper,
- secondaryApp: SplitScreenHelper
- ) {
- tapl.workspace.switchToOverview().dismissAllTasks()
- primaryApp.launchViaIntent(wmHelper)
- secondaryApp.launchViaIntent(wmHelper)
- tapl.goHome()
- wmHelper.StateSyncBuilder()
- .withHomeActivityVisible()
- .waitForAndVerify()
- splitFromOverview(tapl)
- waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
- }
-
- fun splitFromOverview(tapl: LauncherInstrumentation) {
- // Note: The initial split position in landscape is different between tablet and phone.
- // In landscape, tablet will let the first app split to right side, and phone will
- // split to left side.
- if (tapl.isTablet) {
- tapl.workspace.switchToOverview().overviewActions
- .clickSplit()
- .currentTask
- .open()
- } else {
- tapl.workspace.switchToOverview().currentTask
- .tapMenu()
- .tapSplitMenuItem()
- .currentTask
- .open()
- }
- SystemClock.sleep(TIMEOUT_MS)
- }
-
- fun dragFromNotificationToSplit(
- instrumentation: Instrumentation,
- device: UiDevice,
- wmHelper: WindowManagerStateHelper
- ) {
- val displayBounds = wmHelper.currentState.layerState
- .displays.firstOrNull { !it.isVirtual }
- ?.layerStackSpace
- ?: error("Display not found")
-
- // Pull down the notifications
- device.swipe(
- displayBounds.centerX(), 5,
- displayBounds.centerX(), displayBounds.bottom, 20 /* steps */
- )
- SystemClock.sleep(TIMEOUT_MS)
-
- // Find the target notification
- val notificationScroller = device.wait(
- Until.findObject(notificationScrollerSelector), TIMEOUT_MS
- )
- var notificationContent = notificationScroller.findObject(notificationContentSelector)
-
- while (notificationContent == null) {
- device.swipe(
- displayBounds.centerX(), displayBounds.centerY(),
- displayBounds.centerX(), displayBounds.centerY() - 150, 20 /* steps */
- )
- notificationContent = notificationScroller.findObject(notificationContentSelector)
- }
-
- // Drag to split
- val dragStart = notificationContent.visibleCenter
- val dragMiddle = Point(dragStart.x + 50, dragStart.y)
- val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
- val downTime = SystemClock.uptimeMillis()
-
- touch(
- instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime,
- TIMEOUT_MS, dragStart
- )
- // It needs a horizontal movement to trigger the drag
- touchMove(
- instrumentation, downTime, SystemClock.uptimeMillis(),
- DRAG_DURATION_MS, dragStart, dragMiddle
- )
- touchMove(
- instrumentation, downTime, SystemClock.uptimeMillis(),
- DRAG_DURATION_MS, dragMiddle, dragEnd
- )
- // Wait for a while to start splitting
- SystemClock.sleep(TIMEOUT_MS)
- touch(
- instrumentation, MotionEvent.ACTION_UP, downTime, SystemClock.uptimeMillis(),
- GESTURE_STEP_MS, dragEnd
- )
- SystemClock.sleep(TIMEOUT_MS)
- }
-
- fun touch(
- instrumentation: Instrumentation,
- action: Int,
- downTime: Long,
- eventTime: Long,
- duration: Long,
- point: Point
- ) {
- val motionEvent = MotionEvent.obtain(
- downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0
- )
- motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
- instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
- motionEvent.recycle()
- SystemClock.sleep(duration)
- }
-
- fun touchMove(
- instrumentation: Instrumentation,
- downTime: Long,
- eventTime: Long,
- duration: Long,
- from: Point,
- to: Point
- ) {
- val steps: Long = duration / GESTURE_STEP_MS
- var currentTime = eventTime
- var currentX = from.x.toFloat()
- var currentY = from.y.toFloat()
- val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
- val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
-
- for (i in 1..steps) {
- val motionMove = MotionEvent.obtain(
- downTime, currentTime, MotionEvent.ACTION_MOVE, currentX, currentY, 0
- )
- motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
- instrumentation.uiAutomation.injectInputEvent(motionMove, true)
- motionMove.recycle()
-
- currentTime += GESTURE_STEP_MS
- if (i == steps - 1) {
- currentX = to.x.toFloat()
- currentY = to.y.toFloat()
- } else {
- currentX += stepX
- currentY += stepY
- }
- SystemClock.sleep(GESTURE_STEP_MS)
- }
- }
-
- fun longPress(
- instrumentation: Instrumentation,
- point: Point
- ) {
- val downTime = SystemClock.uptimeMillis()
- touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, point)
- SystemClock.sleep(LONG_PRESS_TIME_MS)
- touch(instrumentation, MotionEvent.ACTION_UP, downTime, downTime, TIMEOUT_MS, point)
- }
-
- fun createShortcutOnHotseatIfNotExist(
- tapl: LauncherInstrumentation,
- appName: String
- ) {
- tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
- val allApps = tapl.workspace.switchToAllApps()
- allApps.freeze()
- try {
- allApps.getAppIcon(appName).dragToHotseat(0)
- } finally {
- allApps.unfreeze()
- }
- }
-
- fun dragDividerToResizeAndWait(
- device: UiDevice,
- wmHelper: WindowManagerStateHelper
- ) {
- val displayBounds = wmHelper.currentState.layerState
- .displays.firstOrNull { !it.isVirtual }
- ?.layerStackSpace
- ?: error("Display not found")
- val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
- dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3))
-
- wmHelper.StateSyncBuilder()
- .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
- .waitForAndVerify()
- }
-
- fun dragDividerToDismissSplit(
- device: UiDevice,
- wmHelper: WindowManagerStateHelper,
- dragToRight: Boolean,
- dragToBottom: Boolean
- ) {
- val displayBounds = wmHelper.currentState.layerState
- .displays.firstOrNull { !it.isVirtual }
- ?.layerStackSpace
- ?: error("Display not found")
- val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
- dividerBar.drag(Point(
- if (dragToRight) {
- displayBounds.width * 4 / 5
- } else {
- displayBounds.width * 1 / 5
- },
- if (dragToBottom) {
- displayBounds.height * 4 / 5
- } else {
- displayBounds.height * 1 / 5
- }))
- }
-
- fun doubleTapDividerToSwitch(device: UiDevice) {
- val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
- val interval = (ViewConfiguration.getDoubleTapTimeout() +
- ViewConfiguration.getDoubleTapMinTime()) / 2
- dividerBar.click()
- SystemClock.sleep(interval.toLong())
- dividerBar.click()
- }
-
- fun copyContentInSplit(
- instrumentation: Instrumentation,
- device: UiDevice,
- sourceApp: IComponentNameMatcher,
- destinationApp: IComponentNameMatcher,
- ) {
- // Copy text from sourceApp
- val textView = device.wait(Until.findObject(
- By.res(sourceApp.packageName, "SplitScreenTest")), TIMEOUT_MS)
- longPress(instrumentation, textView.getVisibleCenter())
-
- val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
- copyBtn.click()
-
- // Paste text to destinationApp
- val editText = device.wait(Until.findObject(
- By.res(destinationApp.packageName, "plain_text_input")), TIMEOUT_MS)
- longPress(instrumentation, editText.getVisibleCenter())
-
- val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
- pasteBtn.click()
-
- // Verify text
- if (!textView.getText().contentEquals(editText.getText())) {
- error("Fail to copy content in split")
- }
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt
deleted file mode 100644
index f9b0800..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2021 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.
- */
-
-@file:JvmName("CommonAssertions")
-package com.android.wm.shell.flicker.pip
-
-internal const val PIP_WINDOW_COMPONENT = "PipMenuActivity"
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index 87d800c..4788507 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -28,16 +28,17 @@
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.entireScreenCovered
+import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
+import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
+import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
-import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -72,7 +73,7 @@
class EnterPipToOtherOrientationTest(
testSpec: FlickerTestParameter
) : PipTransition(testSpec) {
- private val testApp = FixedAppHelper(instrumentation)
+ private val testApp = FixedOrientationAppHelper(instrumentation)
private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
@@ -127,7 +128,7 @@
}
/**
- * Checks that the [ComponentMatcher.NAV_BAR] has the correct position at
+ * Checks that the [ComponentNameMatcher.NAV_BAR] has the correct position at
* the start and end of the transition
*/
@FlakyTest
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
index 45851c8..6285991 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -18,14 +18,14 @@
import android.platform.test.annotations.Presubmit
import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
+import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
import org.junit.Test
/**
* Base class for pip expand tests
*/
abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
- protected val testApp = FixedAppHelper(instrumentation)
+ protected val testApp = FixedOrientationAppHelper(instrumentation)
/**
* Checks that the pip app window remains inside the display bounds throughout the whole
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
index 19bdca5..5f94196 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
@@ -18,9 +18,9 @@
import android.platform.test.annotations.Presubmit
import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
import com.android.server.wm.flicker.traces.region.RegionSubject
import com.android.wm.shell.flicker.Direction
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
import org.junit.Test
/**
@@ -29,7 +29,7 @@
abstract class MovePipShelfHeightTransition(
testSpec: FlickerTestParameter
) : PipTransition(testSpec) {
- protected val testApp = FixedAppHelper(instrumentation)
+ protected val testApp = FixedOrientationAppHelper(instrumentation)
/**
* Checks [pipApp] window remains visible throughout the animation
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 3d3b53d..2aa0da9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -24,11 +24,11 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.traces.common.ComponentNameMatcher
-import com.android.wm.shell.flicker.helpers.ImeAppHelper
import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index f13698f..0fce64e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -25,10 +25,10 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -62,7 +62,7 @@
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group4
open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
- private val fixedApp = FixedAppHelper(instrumentation)
+ private val testApp = SimpleAppHelper(instrumentation)
private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.startRotation)
private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.endRotation)
@@ -74,7 +74,7 @@
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition {
setup {
- fixedApp.launchViaIntent(wmHelper)
+ testApp.launchViaIntent(wmHelper)
setRotation(testSpec.startRotation)
}
transitions {
@@ -90,48 +90,48 @@
override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/**
- * Checks that [fixedApp] layer is within [screenBoundsStart] at the start of the transition
+ * Checks that [testApp] layer is within [screenBoundsStart] at the start of the transition
*/
@Presubmit
@Test
fun fixedAppLayer_StartingBounds() {
testSpec.assertLayersStart {
- visibleRegion(fixedApp).coversAtMost(screenBoundsStart)
+ visibleRegion(testApp).coversAtMost(screenBoundsStart)
}
}
/**
- * Checks that [fixedApp] layer is within [screenBoundsEnd] at the end of the transition
+ * Checks that [testApp] layer is within [screenBoundsEnd] at the end of the transition
*/
@Presubmit
@Test
fun fixedAppLayer_EndingBounds() {
testSpec.assertLayersEnd {
- visibleRegion(fixedApp).coversAtMost(screenBoundsEnd)
+ visibleRegion(testApp).coversAtMost(screenBoundsEnd)
}
}
/**
- * Checks that [fixedApp] plus [pipApp] layers are within [screenBoundsEnd] at the start
+ * Checks that [testApp] plus [pipApp] layers are within [screenBoundsEnd] at the start
* of the transition
*/
@Presubmit
@Test
fun appLayers_StartingBounds() {
testSpec.assertLayersStart {
- visibleRegion(fixedApp.or(pipApp)).coversExactly(screenBoundsStart)
+ visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsStart)
}
}
/**
- * Checks that [fixedApp] plus [pipApp] layers are within [screenBoundsEnd] at the end
+ * Checks that [testApp] plus [pipApp] layers are within [screenBoundsEnd] at the end
* of the transition
*/
@Presubmit
@Test
fun appLayers_EndingBounds() {
testSpec.assertLayersEnd {
- visibleRegion(fixedApp.or(pipApp)).coversExactly(screenBoundsEnd)
+ visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsEnd)
}
}
@@ -165,26 +165,26 @@
}
/**
- * Ensure that the [pipApp] window does not obscure the [fixedApp] at the start of the
+ * Ensure that the [pipApp] window does not obscure the [testApp] at the start of the
* transition
*/
@Presubmit
@Test
fun pipIsAboveFixedAppWindow_Start() {
testSpec.assertWmStart {
- isAboveWindow(pipApp, fixedApp)
+ isAboveWindow(pipApp, testApp)
}
}
/**
- * Ensure that the [pipApp] window does not obscure the [fixedApp] at the end of the
+ * Ensure that the [pipApp] window does not obscure the [testApp] at the end of the
* transition
*/
@Presubmit
@Test
fun pipIsAboveFixedAppWindow_End() {
testSpec.assertWmEnd {
- isAboveWindow(pipApp, fixedApp)
+ isAboveWindow(pipApp, testApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index b67cf77..ff505a0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -21,12 +21,12 @@
import android.view.Surface
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
+import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.wm.shell.flicker.BaseTest
-import com.android.wm.shell.flicker.helpers.PipAppHelper
-import com.android.wm.shell.flicker.testapp.Components
abstract class PipTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
protected val pipApp = PipAppHelper(instrumentation)
@@ -61,11 +61,11 @@
*
* @param eachRun If the pip app should be launched in each run (otherwise only 1x per test)
* @param stringExtras Arguments to pass to the PIP launch intent
- * @param extraSpec Addicional segment of flicker specification
+ * @param extraSpec Additional segment of flicker specification
*/
@JvmOverloads
protected open fun buildTransition(
- stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true"),
+ stringExtras: Map<String, String> = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true"),
extraSpec: FlickerBuilder.() -> Unit = {}
): FlickerBuilder.() -> Unit {
return {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index 866e4e8..30332f6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -31,9 +31,9 @@
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
-import com.android.wm.shell.flicker.testapp.Components
-import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -68,7 +68,7 @@
pipApp.launchViaIntent(wmHelper, stringExtras = mapOf(
EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()))
// Enter PiP.
- broadcastActionTrigger.doAction(Components.PipActivity.ACTION_ENTER_PIP)
+ broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP)
// System bar may fade out during fixed rotation.
wmHelper.StateSyncBuilder()
.withPipShown()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
new file mode 100644
index 0000000..cdd768a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip.tv
+
+import android.app.Instrumentation
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+
+/**
+ * Helper class for PIP app on AndroidTV
+ */
+open class PipAppHelperTv(instrumentation: Instrumentation) : PipAppHelper(instrumentation) {
+ private val appSelector = By.pkg(`package`).depth(0)
+
+ val ui: UiObject2?
+ get() = uiDevice.findObject(appSelector)
+
+ private fun focusOnObject(selector: BySelector): Boolean {
+ // We expect all the focusable UI elements to be arranged in a way so that it is possible
+ // to "cycle" over all them by clicking the D-Pad DOWN button, going back up to "the top"
+ // from "the bottom".
+ repeat(FOCUS_ATTEMPTS) {
+ uiDevice.findObject(selector)?.apply { if (isFocusedOrHasFocusedChild) return true }
+ ?: error("The object we try to focus on is gone.")
+
+ uiDevice.pressDPadDown()
+ uiDevice.waitForIdle()
+ }
+ return false
+ }
+
+ override fun clickObject(resId: String) {
+ val selector = By.res(`package`, resId)
+ focusOnObject(selector) || error("Could not focus on `$resId` object")
+ uiDevice.pressDPadCenter()
+ }
+
+ @Deprecated(
+ "Use PipAppHelper.closePipWindow(wmHelper) instead",
+ ReplaceWith("closePipWindow(wmHelper)")
+ )
+ override fun closePipWindow() {
+ uiDevice.closeTvPipWindow()
+ }
+
+ /**
+ * Taps the pip window and dismisses it by clicking on the X button.
+ */
+ override fun closePipWindow(wmHelper: WindowManagerStateHelper) {
+ uiDevice.closeTvPipWindow()
+
+ // Wait for animation to complete.
+ wmHelper.StateSyncBuilder()
+ .withPipGone()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ }
+
+ fun waitUntilClosed(): Boolean {
+ val appSelector = By.pkg(`package`).depth(0)
+ return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS)
+ }
+
+ companion object {
+ private const val FOCUS_ATTEMPTS = 20
+ private const val APP_CLOSE_WAIT_TIME_MS = 3_000L
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
index 180ced0..a16f5f6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
@@ -21,7 +21,6 @@
import android.view.Surface
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
-import com.android.wm.shell.flicker.helpers.PipAppHelper
import org.junit.Before
import org.junit.runners.Parameterized
@@ -38,7 +37,7 @@
hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)
}
}
- protected val testApp = PipAppHelper(instrumentation)
+ protected val testApp = PipAppHelperTv(instrumentation)
@Before
open fun televisionSetUp() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
index 4be19d6..68dbbfb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
@@ -19,8 +19,8 @@
import android.graphics.Rect
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.UiObject2
+import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.testapp.Components
import com.android.wm.shell.flicker.wait
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
@@ -165,44 +165,44 @@
enterPip_openMenu_assertShown()
// PiP menu should contain "No-Op", "Off" and "Clear" buttons...
- uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_NO_OP)
+ uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_NO_OP)
?: fail("\"No-Op\" button should be shown in Pip menu")
- uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_OFF)
+ uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF)
?: fail("\"Off\" button should be shown in Pip menu")
- uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
+ uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
?: fail("\"Clear\" button should be shown in Pip menu")
// ... and should also contain the "Full screen" and "Close" buttons.
assertFullscreenAndCloseButtonsAreShown()
- uiDevice.clickTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_OFF)
+ uiDevice.clickTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF)
// Invoking the "Off" action should replace it with the "On" action/button and should
// remove the "No-Op" action/button. "Clear" action/button should remain in the menu ...
- uiDevice.waitForTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_ON)
+ uiDevice.waitForTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_ON)
?: fail("\"On\" button should be shown in Pip for a corresponding custom action")
assertNull("\"No-Op\" button should not be shown in Pip menu",
uiDevice.findTvPipMenuElementWithDescription(
- Components.PipActivity.MENU_ACTION_NO_OP))
- uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
+ ActivityOptions.Pip.MENU_ACTION_NO_OP))
+ uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
?: fail("\"Clear\" button should be shown in Pip menu")
// ... as well as the "Full screen" and "Close" buttons.
assertFullscreenAndCloseButtonsAreShown()
- uiDevice.clickTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
+ uiDevice.clickTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
// Invoking the "Clear" action should remove all the custom actions and their corresponding
// buttons, ...
uiDevice.waitUntilTvPipMenuElementWithDescriptionIsGone(
- Components.PipActivity.MENU_ACTION_ON)?.also {
+ ActivityOptions.Pip.MENU_ACTION_ON)?.also {
isGone -> if (!isGone) fail("\"On\" button should not be shown in Pip menu")
}
assertNull("\"Off\" button should not be shown in Pip menu",
uiDevice.findTvPipMenuElementWithDescription(
- Components.PipActivity.MENU_ACTION_OFF))
+ ActivityOptions.Pip.MENU_ACTION_OFF))
assertNull("\"Clear\" button should not be shown in Pip menu",
uiDevice.findTvPipMenuElementWithDescription(
- Components.PipActivity.MENU_ACTION_CLEAR))
+ ActivityOptions.Pip.MENU_ACTION_CLEAR))
assertNull("\"No-Op\" button should not be shown in Pip menu",
uiDevice.findTvPipMenuElementWithDescription(
- Components.PipActivity.MENU_ACTION_NO_OP))
+ ActivityOptions.Pip.MENU_ACTION_NO_OP))
// ... but the menu should still contain the "Full screen" and "Close" buttons.
assertFullscreenAndCloseButtonsAreShown()
@@ -217,11 +217,11 @@
enterPip_openMenu_assertShown()
// PiP menu should contain "No-Op", "Off" and "Clear" buttons for the custom actions...
- uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_NO_OP)
+ uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_NO_OP)
?: fail("\"No-Op\" button should be shown in Pip menu")
- uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_OFF)
+ uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF)
?: fail("\"Off\" button should be shown in Pip menu")
- uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
+ uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
?: fail("\"Clear\" button should be shown in Pip menu")
// ... should also contain the "Full screen" and "Close" buttons, ...
assertFullscreenAndCloseButtonsAreShown()
@@ -231,7 +231,7 @@
assertNull("\"Pause\" button should not be shown in menu when there are custom actions",
uiDevice.findTvPipMenuElementWithDescription(pauseButtonDescription))
- uiDevice.clickTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
+ uiDevice.clickTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
// Invoking the "Clear" action should remove all the custom actions, which should bring up
// media buttons...
uiDevice.waitForTvPipMenuElementWithDescription(pauseButtonDescription)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index 3c439fd..7dbd279 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -28,7 +28,6 @@
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowKeepVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
import org.junit.FixMethodOrder
@@ -48,16 +47,16 @@
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
- private val textEditApp = SplitScreenHelper.getIme(instrumentation)
+ private val textEditApp = SplitScreenUtils.getIme(instrumentation)
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
- SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, textEditApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, textEditApp)
}
transitions {
- SplitScreenHelper.copyContentInSplit(
+ SplitScreenUtils.copyContentInSplit(
instrumentation, device, primaryApp, textEditApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 60e5f78..3646fd7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -30,7 +30,6 @@
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesInvisible
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerBecomesInvisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
@@ -57,14 +56,14 @@
get() = {
super.transition(this)
setup {
- SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
}
transitions {
if (tapl.isTablet) {
- SplitScreenHelper.dragDividerToDismissSplit(device, wmHelper,
+ SplitScreenUtils.dragDividerToDismissSplit(device, wmHelper,
dragToRight = false, dragToBottom = true)
} else {
- SplitScreenHelper.dragDividerToDismissSplit(device, wmHelper,
+ SplitScreenUtils.dragDividerToDismissSplit(device, wmHelper,
dragToRight = true, dragToBottom = true)
}
wmHelper.StateSyncBuilder()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 2db3009..80abedd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -28,7 +28,6 @@
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.wm.shell.flicker.appWindowBecomesInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerBecomesInvisible
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
@@ -56,7 +55,7 @@
get() = {
super.transition(this)
setup {
- SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
}
transitions {
tapl.goHome()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index fddd84c..2915787 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -29,7 +29,6 @@
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowKeepVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
import org.junit.FixMethodOrder
@@ -54,10 +53,10 @@
get() = {
super.transition(this)
setup {
- SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
}
transitions {
- SplitScreenHelper.dragDividerToResizeAndWait(device, wmHelper)
+ SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index a7c6898..8e041a7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -30,7 +30,6 @@
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
@@ -76,7 +75,7 @@
.openAllApps()
.getAppIcon(secondaryApp.appName)
.dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
- SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index 7d8a8db..047bcfe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -31,7 +31,6 @@
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
@@ -60,7 +59,7 @@
testSpec: FlickerTestParameter
) : SplitScreenBase(testSpec) {
- private val sendNotificationApp = SplitScreenHelper.getSendNotification(instrumentation)
+ private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
@Before
fun before() {
@@ -76,7 +75,7 @@
sendNotificationApp.launchViaIntent(wmHelper)
val sendNotification = device.wait(
Until.findObject(By.text("Send Notification")),
- SplitScreenHelper.TIMEOUT_MS
+ SplitScreenUtils.TIMEOUT_MS
)
sendNotification?.click() ?: error("Send notification button not found")
@@ -84,8 +83,8 @@
primaryApp.launchViaIntent(wmHelper)
}
transitions {
- SplitScreenHelper.dragFromNotificationToSplit(instrumentation, device, wmHelper)
- SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
+ SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
}
teardown {
sendNotificationApp.exit(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index bfd8a3a..a11874e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -30,7 +30,6 @@
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
@@ -70,7 +69,7 @@
super.transition(this)
setup {
tapl.goHome()
- SplitScreenHelper.createShortcutOnHotseatIfNotExist(
+ SplitScreenUtils.createShortcutOnHotseatIfNotExist(
tapl, secondaryApp.appName
)
primaryApp.launchViaIntent(wmHelper)
@@ -79,7 +78,7 @@
tapl.launchedAppState.taskbar
.getAppIcon(secondaryApp.appName)
.dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
- SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index cefb9f5..6064b52 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -26,7 +26,6 @@
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
@@ -64,8 +63,8 @@
.waitForAndVerify()
}
transitions {
- SplitScreenHelper.splitFromOverview(tapl)
- SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ SplitScreenUtils.splitFromOverview(tapl)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
index eab473c..e6d6379 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
@@ -21,12 +21,11 @@
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.setRotation
import com.android.wm.shell.flicker.BaseTest
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
abstract class SplitScreenBase(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
protected val context: Context = instrumentation.context
- protected val primaryApp = SplitScreenHelper.getPrimary(instrumentation)
- protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
+ protected val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ protected val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
new file mode 100644
index 0000000..96b9faa
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.app.Instrumentation
+import android.graphics.Point
+import android.os.SystemClock
+import android.view.InputDevice
+import android.view.MotionEvent
+import android.view.ViewConfiguration
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.IComponentNameMatcher
+import com.android.server.wm.traces.parser.toFlickerComponent
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+
+internal object SplitScreenUtils {
+ internal const val TIMEOUT_MS = 3_000L
+ private const val DRAG_DURATION_MS = 1_000L
+ private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
+ private const val DIVIDER_BAR = "docked_divider_handle"
+ private const val GESTURE_STEP_MS = 16L
+ private const val LONG_PRESS_TIME_MS = 100L
+ private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
+
+ private val notificationScrollerSelector: BySelector
+ get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
+ private val notificationContentSelector: BySelector
+ get() = By.text("Notification content")
+ private val dividerBarSelector: BySelector
+ get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
+
+ fun getPrimary(instrumentation: Instrumentation): StandardAppHelper =
+ SimpleAppHelper(
+ instrumentation,
+ ActivityOptions.SplitScreen.Primary.LABEL,
+ ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+ )
+
+ fun getSecondary(instrumentation: Instrumentation): StandardAppHelper =
+ SimpleAppHelper(
+ instrumentation,
+ ActivityOptions.SplitScreen.Secondary.LABEL,
+ ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent()
+ )
+
+ fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper =
+ NonResizeableAppHelper(instrumentation)
+
+ fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper =
+ NotificationAppHelper(instrumentation)
+
+ fun getIme(instrumentation: Instrumentation): ImeAppHelper =
+ ImeAppHelper(instrumentation)
+
+ fun waitForSplitComplete(
+ wmHelper: WindowManagerStateHelper,
+ primaryApp: IComponentMatcher,
+ secondaryApp: IComponentMatcher,
+ ) {
+ wmHelper.StateSyncBuilder()
+ .withWindowSurfaceAppeared(primaryApp)
+ .withWindowSurfaceAppeared(secondaryApp)
+ .withSplitDividerVisible()
+ .waitForAndVerify()
+ }
+
+ fun enterSplit(
+ wmHelper: WindowManagerStateHelper,
+ tapl: LauncherInstrumentation,
+ primaryApp: StandardAppHelper,
+ secondaryApp: StandardAppHelper
+ ) {
+ tapl.workspace.switchToOverview().dismissAllTasks()
+ primaryApp.launchViaIntent(wmHelper)
+ secondaryApp.launchViaIntent(wmHelper)
+ tapl.goHome()
+ wmHelper.StateSyncBuilder()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ splitFromOverview(tapl)
+ waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
+ fun splitFromOverview(tapl: LauncherInstrumentation) {
+ // Note: The initial split position in landscape is different between tablet and phone.
+ // In landscape, tablet will let the first app split to right side, and phone will
+ // split to left side.
+ if (tapl.isTablet) {
+ tapl.workspace.switchToOverview().overviewActions
+ .clickSplit()
+ .currentTask
+ .open()
+ } else {
+ tapl.workspace.switchToOverview().currentTask
+ .tapMenu()
+ .tapSplitMenuItem()
+ .currentTask
+ .open()
+ }
+ SystemClock.sleep(TIMEOUT_MS)
+ }
+
+ fun dragFromNotificationToSplit(
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ wmHelper: WindowManagerStateHelper
+ ) {
+ val displayBounds = wmHelper.currentState.layerState
+ .displays.firstOrNull { !it.isVirtual }
+ ?.layerStackSpace
+ ?: error("Display not found")
+
+ // Pull down the notifications
+ device.swipe(
+ displayBounds.centerX(), 5,
+ displayBounds.centerX(), displayBounds.bottom, 20 /* steps */
+ )
+ SystemClock.sleep(TIMEOUT_MS)
+
+ // Find the target notification
+ val notificationScroller = device.wait(
+ Until.findObject(notificationScrollerSelector), TIMEOUT_MS
+ )
+ var notificationContent = notificationScroller.findObject(notificationContentSelector)
+
+ while (notificationContent == null) {
+ device.swipe(
+ displayBounds.centerX(), displayBounds.centerY(),
+ displayBounds.centerX(), displayBounds.centerY() - 150, 20 /* steps */
+ )
+ notificationContent = notificationScroller.findObject(notificationContentSelector)
+ }
+
+ // Drag to split
+ val dragStart = notificationContent.visibleCenter
+ val dragMiddle = Point(dragStart.x + 50, dragStart.y)
+ val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
+ val downTime = SystemClock.uptimeMillis()
+
+ touch(
+ instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime,
+ TIMEOUT_MS, dragStart
+ )
+ // It needs a horizontal movement to trigger the drag
+ touchMove(
+ instrumentation, downTime, SystemClock.uptimeMillis(),
+ DRAG_DURATION_MS, dragStart, dragMiddle
+ )
+ touchMove(
+ instrumentation, downTime, SystemClock.uptimeMillis(),
+ DRAG_DURATION_MS, dragMiddle, dragEnd
+ )
+ // Wait for a while to start splitting
+ SystemClock.sleep(TIMEOUT_MS)
+ touch(
+ instrumentation, MotionEvent.ACTION_UP, downTime, SystemClock.uptimeMillis(),
+ GESTURE_STEP_MS, dragEnd
+ )
+ SystemClock.sleep(TIMEOUT_MS)
+ }
+
+ fun touch(
+ instrumentation: Instrumentation,
+ action: Int,
+ downTime: Long,
+ eventTime: Long,
+ duration: Long,
+ point: Point
+ ) {
+ val motionEvent = MotionEvent.obtain(
+ downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0
+ )
+ motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
+ instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
+ motionEvent.recycle()
+ SystemClock.sleep(duration)
+ }
+
+ fun touchMove(
+ instrumentation: Instrumentation,
+ downTime: Long,
+ eventTime: Long,
+ duration: Long,
+ from: Point,
+ to: Point
+ ) {
+ val steps: Long = duration / GESTURE_STEP_MS
+ var currentTime = eventTime
+ var currentX = from.x.toFloat()
+ var currentY = from.y.toFloat()
+ val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
+ val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
+
+ for (i in 1..steps) {
+ val motionMove = MotionEvent.obtain(
+ downTime, currentTime, MotionEvent.ACTION_MOVE, currentX, currentY, 0
+ )
+ motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
+ instrumentation.uiAutomation.injectInputEvent(motionMove, true)
+ motionMove.recycle()
+
+ currentTime += GESTURE_STEP_MS
+ if (i == steps - 1) {
+ currentX = to.x.toFloat()
+ currentY = to.y.toFloat()
+ } else {
+ currentX += stepX
+ currentY += stepY
+ }
+ SystemClock.sleep(GESTURE_STEP_MS)
+ }
+ }
+
+ fun longPress(
+ instrumentation: Instrumentation,
+ point: Point
+ ) {
+ val downTime = SystemClock.uptimeMillis()
+ touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, point)
+ SystemClock.sleep(LONG_PRESS_TIME_MS)
+ touch(instrumentation, MotionEvent.ACTION_UP, downTime, downTime, TIMEOUT_MS, point)
+ }
+
+ fun createShortcutOnHotseatIfNotExist(
+ tapl: LauncherInstrumentation,
+ appName: String
+ ) {
+ tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
+ val allApps = tapl.workspace.switchToAllApps()
+ allApps.freeze()
+ try {
+ allApps.getAppIcon(appName).dragToHotseat(0)
+ } finally {
+ allApps.unfreeze()
+ }
+ }
+
+ fun dragDividerToResizeAndWait(
+ device: UiDevice,
+ wmHelper: WindowManagerStateHelper
+ ) {
+ val displayBounds = wmHelper.currentState.layerState
+ .displays.firstOrNull { !it.isVirtual }
+ ?.layerStackSpace
+ ?: error("Display not found")
+ val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+ dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3))
+
+ wmHelper.StateSyncBuilder()
+ .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
+ .waitForAndVerify()
+ }
+
+ fun dragDividerToDismissSplit(
+ device: UiDevice,
+ wmHelper: WindowManagerStateHelper,
+ dragToRight: Boolean,
+ dragToBottom: Boolean
+ ) {
+ val displayBounds = wmHelper.currentState.layerState
+ .displays.firstOrNull { !it.isVirtual }
+ ?.layerStackSpace
+ ?: error("Display not found")
+ val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+ dividerBar.drag(Point(
+ if (dragToRight) {
+ displayBounds.width * 4 / 5
+ } else {
+ displayBounds.width * 1 / 5
+ },
+ if (dragToBottom) {
+ displayBounds.height * 4 / 5
+ } else {
+ displayBounds.height * 1 / 5
+ }))
+ }
+
+ fun doubleTapDividerToSwitch(device: UiDevice) {
+ val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+ val interval = (ViewConfiguration.getDoubleTapTimeout() +
+ ViewConfiguration.getDoubleTapMinTime()) / 2
+ dividerBar.click()
+ SystemClock.sleep(interval.toLong())
+ dividerBar.click()
+ }
+
+ fun copyContentInSplit(
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ sourceApp: IComponentNameMatcher,
+ destinationApp: IComponentNameMatcher,
+ ) {
+ // Copy text from sourceApp
+ val textView = device.wait(Until.findObject(
+ By.res(sourceApp.packageName, "SplitScreenTest")), TIMEOUT_MS)
+ longPress(instrumentation, textView.visibleCenter)
+
+ val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
+ copyBtn.click()
+
+ // Paste text to destinationApp
+ val editText = device.wait(Until.findObject(
+ By.res(destinationApp.packageName, "plain_text_input")), TIMEOUT_MS)
+ longPress(instrumentation, editText.visibleCenter)
+
+ val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
+ pasteBtn.click()
+
+ // Verify text
+ if (!textView.text.contentEquals(editText.text)) {
+ error("Fail to copy content in split")
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index 2d5d2b7..6697b64 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -29,7 +29,6 @@
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
@@ -55,10 +54,10 @@
get() = {
super.transition(this)
setup {
- SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
}
transitions {
- SplitScreenHelper.doubleTapDividerToSwitch(device)
+ SplitScreenUtils.doubleTapDividerToSwitch(device)
wmHelper.StateSyncBuilder()
.withAppTransitionIdle()
.waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 20c6af7..5c30116 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -27,7 +27,6 @@
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
@@ -48,13 +47,13 @@
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
- val thirdApp = SplitScreenHelper.getNonResizeable(instrumentation)
+ val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
- SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
thirdApp.launchViaIntent(wmHelper)
wmHelper.StateSyncBuilder()
@@ -63,7 +62,7 @@
}
transitions {
tapl.launchedAppState.quickSwitchToPreviousApp()
- SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index cb9ca9f..9c66a37 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -27,7 +27,6 @@
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
@@ -53,7 +52,7 @@
get() = {
super.transition(this)
setup {
- SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
tapl.goHome()
wmHelper.StateSyncBuilder()
@@ -62,7 +61,7 @@
}
transitions {
tapl.workspace.quickSwitchToPreviousApp()
- SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index 2662767..e8862bd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -27,7 +27,6 @@
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
@@ -53,7 +52,7 @@
get() = {
super.transition(this)
setup {
- SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
tapl.goHome()
wmHelper.StateSyncBuilder()
@@ -64,7 +63,7 @@
tapl.workspace.switchToOverview()
.currentTask
.open()
- SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp
deleted file mode 100644
index ea606df..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
- name: "WMShellFlickerTestApp",
- srcs: ["**/*.java"],
- sdk_version: "current",
- test_suites: ["device-tests"],
-}
-
-java_library {
- name: "wmshell-flicker-test-components",
- srcs: ["src/**/Components.java"],
- sdk_version: "test_current",
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
deleted file mode 100644
index bc0b0b6..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
+++ /dev/null
@@ -1,147 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.wm.shell.flicker.testapp">
-
- <uses-sdk android:minSdkVersion="29"
- android:targetSdkVersion="29"/>
- <application android:allowBackup="false"
- android:supportsRtl="true">
- <activity android:name=".FixedActivity"
- android:resizeableActivity="true"
- android:supportsPictureInPicture="true"
- android:launchMode="singleTop"
- android:theme="@style/CutoutShortEdges"
- android:label="FixedApp"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- <activity android:name=".PipActivity"
- android:resizeableActivity="true"
- android:supportsPictureInPicture="true"
- android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
- android:taskAffinity="com.android.wm.shell.flicker.testapp.PipActivity"
- android:theme="@style/CutoutShortEdges"
- android:launchMode="singleTop"
- android:label="PipApp"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
- </intent-filter>
- </activity>
-
- <activity android:name=".ImeActivity"
- android:taskAffinity="com.android.wm.shell.flicker.testapp.ImeActivity"
- android:theme="@style/CutoutShortEdges"
- android:label="ImeApp"
- android:launchMode="singleTop"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
- </intent-filter>
- </activity>
-
- <activity android:name=".SplitScreenActivity"
- android:resizeableActivity="true"
- android:taskAffinity="com.android.wm.shell.flicker.testapp.SplitScreenActivity"
- android:theme="@style/CutoutShortEdges"
- android:label="SplitScreenPrimaryApp"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
-
- <activity android:name=".SplitScreenSecondaryActivity"
- android:resizeableActivity="true"
- android:taskAffinity="com.android.wm.shell.flicker.testapp.SplitScreenSecondaryActivity"
- android:theme="@style/CutoutShortEdges"
- android:label="SplitScreenSecondaryApp"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
-
- <activity android:name=".SendNotificationActivity"
- android:taskAffinity="com.android.wm.shell.flicker.testapp.SendNotificationActivity"
- android:theme="@style/CutoutShortEdges"
- android:label="SendNotificationApp"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
-
- <activity android:name=".NonResizeableActivity"
- android:resizeableActivity="false"
- android:taskAffinity="com.android.wm.shell.flicker.testapp.NonResizeableActivity"
- android:theme="@style/CutoutShortEdges"
- android:label="NonResizeableApp"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
-
- <activity android:name=".SimpleActivity"
- android:taskAffinity="com.android.wm.shell.flicker.testapp.SimpleActivity"
- android:theme="@style/CutoutShortEdges"
- android:label="SimpleApp"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- <activity
- android:name=".LaunchBubbleActivity"
- android:label="LaunchBubbleApp"
- android:exported="true"
- android:theme="@style/CutoutShortEdges"
- android:launchMode="singleTop">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <action android:name="android.intent.action.VIEW" />
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- <activity
- android:name=".BubbleActivity"
- android:label="BubbleApp"
- android:exported="false"
- android:theme="@style/CutoutShortEdges"
- android:resizeableActivity="true" />
- </application>
-</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml
deleted file mode 100644
index b43f31d..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2021 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7.2,14.4m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M14.8,18m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.2,8.8m-4.8,0a4.8,4.8 0,1 1,9.6 0a4.8,4.8 0,1 1,-9.6 0"/>
-</vector>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml
deleted file mode 100644
index 0e8c7a0..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2021 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
- <path
- android:pathData="M12,4c-4.97,0 -9,3.58 -9,8c0,1.53 0.49,2.97 1.33,4.18c0.12,0.18 0.2,0.46 0.1,0.66c-0.33,0.68 -0.79,1.52 -1.38,2.39c-0.12,0.17 0.01,0.41 0.21,0.39c0.63,-0.05 1.86,-0.26 3.38,-0.91c0.17,-0.07 0.36,-0.06 0.52,0.03C8.55,19.54 10.21,20 12,20c4.97,0 9,-3.58 9,-8S16.97,4 12,4zM16.94,11.63l-3.29,3.29c-0.13,0.13 -0.34,0.04 -0.34,-0.14v-1.57c0,-0.11 -0.1,-0.21 -0.21,-0.2c-2.19,0.06 -3.65,0.65 -5.14,1.95c-0.15,0.13 -0.38,0 -0.33,-0.19c0.7,-2.57 2.9,-4.57 5.5,-4.75c0.1,-0.01 0.18,-0.09 0.18,-0.19V8.2c0,-0.18 0.22,-0.27 0.34,-0.14l3.29,3.29C17.02,11.43 17.02,11.55 16.94,11.63z"
- android:fillColor="#000000"
- android:fillType="evenOdd"/>
-</vector>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml
deleted file mode 100644
index 4708cfd..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2018 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.
--->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:focusableInTouchMode="true"
- android:background="@android:color/holo_green_light">
- <EditText android:id="@+id/plain_text_input"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:inputType="text"/>
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml
deleted file mode 100644
index 45d5917..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="@android:color/holo_orange_light">
-
- <TextView
- android:id="@+id/NonResizeableTest"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:gravity="center_vertical|center_horizontal"
- android:text="NonResizeableActivity"
- android:textAppearance="?android:attr/textAppearanceLarge"/>
-
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml
deleted file mode 100644
index 8d59b56..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2021 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.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="@android:color/black">
-
- <Button
- android:id="@+id/button_send_notification"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:layout_centerVertical="true"
- android:text="Send Notification" />
-</RelativeLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml
deleted file mode 100644
index 5d94e51..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2018 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.
--->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@android:color/holo_orange_light">
-
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml
deleted file mode 100644
index 642a08b5..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="@android:color/holo_green_light">
-
- <TextView
- android:id="@+id/SplitScreenTest"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:gravity="center_vertical|center_horizontal"
- android:textIsSelectable="true"
- android:text="PrimaryActivity"
- android:textAppearance="?android:attr/textAppearanceLarge"/>
-
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml
deleted file mode 100644
index 674bb70..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="@android:color/holo_blue_light">
-
- <TextView
- android:id="@+id/SplitScreenTest"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:gravity="center_vertical|center_horizontal"
- android:text="SecondaryActivity"
- android:textAppearance="?android:attr/textAppearanceLarge"/>
-
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml
deleted file mode 100644
index 23b51cc..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<resources>
- <style name="DefaultTheme" parent="@android:style/Theme.DeviceDefault">
- <item name="android:windowBackground">@android:color/darker_gray</item>
- </style>
-
- <style name="CutoutDefault" parent="@style/DefaultTheme">
- <item name="android:windowLayoutInDisplayCutoutMode">default</item>
- </style>
-
- <style name="CutoutShortEdges" parent="@style/DefaultTheme">
- <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
- </style>
-
- <style name="CutoutNever" parent="@style/DefaultTheme">
- <item name="android:windowLayoutInDisplayCutoutMode">never</item>
- </style>
-</resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
deleted file mode 100644
index a2b580d..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.testapp;
-
-import android.content.ComponentName;
-
-public class Components {
- public static final String PACKAGE_NAME = "com.android.wm.shell.flicker.testapp";
-
- public static class SimpleActivity {
- public static final String LABEL = "SimpleApp";
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".SimpleActivity");
- }
-
- public static class FixedActivity {
- public static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
- public static final String LABEL = "FixedApp";
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".FixedActivity");
- }
-
- public static class NonResizeableActivity {
- public static final String LABEL = "NonResizeableApp";
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".NonResizeableActivity");
- }
-
- public static class PipActivity {
- // Test App > Pip Activity
- public static final String LABEL = "PipApp";
- public static final String MENU_ACTION_NO_OP = "No-Op";
- public static final String MENU_ACTION_ON = "On";
- public static final String MENU_ACTION_OFF = "Off";
- public static final String MENU_ACTION_CLEAR = "Clear";
-
- // Intent action that this activity dynamically registers to enter picture-in-picture
- public static final String ACTION_ENTER_PIP = PACKAGE_NAME + ".PipActivity.ENTER_PIP";
- // Intent action that this activity dynamically registers to set requested orientation.
- // Will apply the oriention to the value set in the EXTRA_FIXED_ORIENTATION extra.
- public static final String ACTION_SET_REQUESTED_ORIENTATION =
- PACKAGE_NAME + ".PipActivity.SET_REQUESTED_ORIENTATION";
-
- // Calls enterPictureInPicture() on creation
- public static final String EXTRA_ENTER_PIP = "enter_pip";
- // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
- public static final String EXTRA_PIP_ORIENTATION = "fixed_orientation";
- // Adds a click listener to finish this activity when it is clicked
- public static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
-
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".PipActivity");
- }
-
- public static class ImeActivity {
- public static final String LABEL = "ImeApp";
- public static final String ACTION_CLOSE_IME =
- PACKAGE_NAME + ".action.CLOSE_IME";
- public static final String ACTION_OPEN_IME =
- PACKAGE_NAME + ".action.OPEN_IME";
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".ImeActivity");
- }
-
- public static class SplitScreenActivity {
- public static final String LABEL = "SplitScreenPrimaryApp";
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".SplitScreenActivity");
- }
-
- public static class SplitScreenSecondaryActivity {
- public static final String LABEL = "SplitScreenSecondaryApp";
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".SplitScreenSecondaryActivity");
- }
-
- public static class SendNotificationActivity {
- public static final String LABEL = "SendNotificationApp";
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".SendNotificationActivity");
- }
-
- public static class LaunchBubbleActivity {
- public static final String LABEL = "LaunchBubbleApp";
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".LaunchBubbleActivity");
- }
-
- public static class BubbleActivity {
- public static final String LABEL = "BubbleApp";
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".BubbleActivity");
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java
deleted file mode 100644
index 59c64a1..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2018 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.testapp;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.inputmethod.InputMethodManager;
-
-public class ImeActivity extends Activity {
- private static final String ACTION_OPEN_IME =
- "com.android.wm.shell.flicker.testapp.action.OPEN_IME";
- private static final String ACTION_CLOSE_IME =
- "com.android.wm.shell.flicker.testapp.action.CLOSE_IME";
-
- private InputMethodManager mImm;
- private View mEditText;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- WindowManager.LayoutParams p = getWindow().getAttributes();
- p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
- .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- getWindow().setAttributes(p);
- setContentView(R.layout.activity_ime);
-
- mEditText = findViewById(R.id.plain_text_input);
- mImm = getSystemService(InputMethodManager.class);
-
- handleIntent(getIntent());
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- handleIntent(intent);
- }
-
- private void handleIntent(Intent intent) {
- final String action = intent.getAction();
- if (ACTION_OPEN_IME.equals(action)) {
- mEditText.requestFocus();
- mImm.showSoftInput(mEditText, InputMethodManager.SHOW_FORCED);
- } else if (ACTION_CLOSE_IME.equals(action)) {
- mImm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
- mEditText.clearFocus();
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/NonResizeableActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/NonResizeableActivity.java
deleted file mode 100644
index 24275e0..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/NonResizeableActivity.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.testapp;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class NonResizeableActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- setContentView(R.layout.activity_non_resizeable);
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java
deleted file mode 100644
index 5343c18..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2018 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.testapp;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class SimpleActivity extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- WindowManager.LayoutParams p = getWindow().getAttributes();
- p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
- .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- getWindow().setAttributes(p);
- setContentView(R.layout.activity_simple);
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index a7234c1..98b5912 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -18,7 +18,9 @@
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
@@ -27,6 +29,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import android.animation.Animator;
import android.window.TransitionInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -76,4 +79,18 @@
verify(mController).onAnimationFinished(mTransition);
}
+
+ @Test
+ public void testChangesBehindStartingWindow() {
+ final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
+ final TransitionInfo.Change embeddingChange = createChange();
+ embeddingChange.setFlags(FLAG_IS_BEHIND_STARTING_WINDOW);
+ info.addChange(embeddingChange);
+ final Animator animator = mAnimRunner.createAnimator(
+ info, mStartTransaction, mFinishTransaction,
+ () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */));
+
+ // The animation should be empty when it is behind starting window.
+ assertEquals(0, animator.getDuration());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 5779425..c628f399 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -32,13 +32,14 @@
import android.os.Handler;
import android.os.IBinder;
import android.testing.AndroidTestingRunner;
+import android.window.DisplayAreaInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransaction.Change;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.ShellExecutor;
@@ -59,7 +60,7 @@
@Mock
private ShellTaskOrganizer mShellTaskOrganizer;
@Mock
- private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
+ private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
@Mock
private ShellExecutor mTestExecutor;
@Mock
@@ -75,7 +76,7 @@
mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer,
- mRootDisplayAreaOrganizer, mMockHandler, mMockTransitions);
+ mRootTaskDisplayAreaOrganizer, mMockHandler, mMockTransitions);
mShellInit.init();
}
@@ -94,19 +95,19 @@
when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
mContext.getDisplayId())).thenReturn(taskWct);
- // Create a fake WCT to simulate setting display windowing mode to freeform
- WindowContainerTransaction displayWct = new WindowContainerTransaction();
+ // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly
MockToken displayMockToken = new MockToken();
- displayWct.setWindowingMode(displayMockToken.token(), WINDOWING_MODE_FREEFORM);
- when(mRootDisplayAreaOrganizer.prepareWindowingModeChange(mContext.getDisplayId(),
- WINDOWING_MODE_FREEFORM)).thenReturn(displayWct);
+ DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken,
+ mContext.getDisplayId(), 0);
+ when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
+ .thenReturn(displayAreaInfo);
// The test
mController.updateDesktopModeActive(true);
ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
WindowContainerTransaction.class);
- verify(mRootDisplayAreaOrganizer).applyTransaction(arg.capture());
+ verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
// WCT should have 2 changes - clear task wm mode and set display wm mode
WindowContainerTransaction wct = arg.getValue();
@@ -118,7 +119,7 @@
assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
// Verify executed WCT has a change for setting display windowing mode to freeform
- Change displayWmModeChange = wct.getChanges().get(displayMockToken.binder());
+ Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
assertThat(displayWmModeChange).isNotNull();
assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
}
@@ -139,19 +140,19 @@
when(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(
mContext.getDisplayId())).thenReturn(taskBoundsWct);
- // Create a fake WCT to simulate setting display windowing mode to fullscreen
- WindowContainerTransaction displayWct = new WindowContainerTransaction();
+ // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly
MockToken displayMockToken = new MockToken();
- displayWct.setWindowingMode(displayMockToken.token(), WINDOWING_MODE_FULLSCREEN);
- when(mRootDisplayAreaOrganizer.prepareWindowingModeChange(mContext.getDisplayId(),
- WINDOWING_MODE_FULLSCREEN)).thenReturn(displayWct);
+ DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken,
+ mContext.getDisplayId(), 0);
+ when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
+ .thenReturn(displayAreaInfo);
// The test
mController.updateDesktopModeActive(false);
ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
WindowContainerTransaction.class);
- verify(mRootDisplayAreaOrganizer).applyTransaction(arg.capture());
+ verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
// WCT should have 3 changes - clear task wm mode and bounds and set display wm mode
WindowContainerTransaction wct = arg.getValue();
@@ -171,7 +172,7 @@
.isTrue();
// Verify executed WCT has a change for setting display windowing mode to fullscreen
- Change displayWmModeChange = wct.getChanges().get(displayMockToken.binder());
+ Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
assertThat(displayWmModeChange).isNotNull();
assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 9240abf..8350870 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -16,7 +16,10 @@
package com.android.wm.shell.splitscreen;
+import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED;
+import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -28,11 +31,10 @@
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -42,8 +44,10 @@
import android.app.ActivityManager;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.Bundle;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -115,7 +119,6 @@
mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
mMainExecutor, Optional.empty()));
- doNothing().when(mStageCoordinator).updateActivityOptions(any(), anyInt());
when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
@@ -303,4 +306,16 @@
verify(mSplitLayout).applySurfaceChanges(any(), any(), any(), any(), any(), eq(false));
}
+
+ @Test
+ public void testAddActivityOptions_addsBackgroundActivitiesFlags() {
+ Bundle options = mStageCoordinator.resolveStartStage(STAGE_TYPE_MAIN,
+ SPLIT_POSITION_UNDEFINED, null /* options */, null /* wct */);
+
+ assertEquals(options.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, WindowContainerToken.class),
+ mMainStage.mRootTaskInfo.token);
+ assertTrue(options.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED));
+ assertTrue(options.getBoolean(
+ KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION));
+ }
}
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 1f96617..cb0f22f 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -238,6 +238,7 @@
ASurfaceControl_createFromWindow; # introduced=29
ASurfaceControl_acquire; # introduced=31
ASurfaceControl_release; # introduced=29
+ ASurfaceControl_fromSurfaceControl; # introduced=34
ASurfaceTexture_acquireANativeWindow; # introduced=28
ASurfaceTexture_attachToGLContext; # introduced=28
ASurfaceTexture_detachFromGLContext; # introduced=28
@@ -255,6 +256,7 @@
ASurfaceTransaction_apply; # introduced=29
ASurfaceTransaction_create; # introduced=29
ASurfaceTransaction_delete; # introduced=29
+ ASurfaceTransaction_fromTransaction; # introduced=34
ASurfaceTransaction_reparent; # introduced=29
ASurfaceTransaction_setBuffer; # introduced=29
ASurfaceTransaction_setBufferAlpha; # introduced=29
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 42f4406..9e4d726 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -17,6 +17,8 @@
#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
#include <android/native_window.h>
#include <android/surface_control.h>
+#include <android/surface_control_jni.h>
+#include <android_runtime/android_view_SurfaceControl.h>
#include <configstore/Utils.h>
#include <gui/HdrMetadata.h>
#include <gui/ISurfaceComposer.h>
@@ -28,6 +30,8 @@
#include <ui/DynamicDisplayInfo.h>
#include <utils/Timers.h>
+#include <utility>
+
using namespace android::hardware::configstore;
using namespace android::hardware::configstore::V1_0;
using namespace android;
@@ -134,6 +138,11 @@
SurfaceControl_release(surfaceControl);
}
+ASurfaceControl* ASurfaceControl_fromSurfaceControl(JNIEnv* env, jobject surfaceControlObj) {
+ return reinterpret_cast<ASurfaceControl*>(
+ android_view_SurfaceControl_getNativeSurfaceControl(env, surfaceControlObj));
+}
+
struct ASurfaceControlStats {
std::variant<int64_t, sp<Fence>> acquireTimeOrFence;
sp<Fence> previousReleaseFence;
@@ -190,6 +199,11 @@
delete transaction;
}
+ASurfaceTransaction* ASurfaceTransaction_fromTransaction(JNIEnv* env, jobject transactionObj) {
+ return reinterpret_cast<ASurfaceTransaction*>(
+ android_view_SurfaceTransaction_getNativeSurfaceTransaction(env, transactionObj));
+}
+
void ASurfaceTransaction_apply(ASurfaceTransaction* aSurfaceTransaction) {
CHECK_NOT_NULL(aSurfaceTransaction);
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
new file mode 100644
index 0000000..ed92af9
--- /dev/null
+++ b/packages/CredentialManager/Android.bp
@@ -0,0 +1,36 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_app {
+ name: "CredentialManager",
+ defaults: ["platform_app_defaults"],
+ certificate: "platform",
+ srcs: ["src/**/*.kt"],
+ resource_dirs: ["res"],
+
+ static_libs: [
+ "androidx.activity_activity-compose",
+ "androidx.appcompat_appcompat",
+ "androidx.compose.material_material",
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.ui_ui",
+ "androidx.compose.ui_ui-tooling",
+ "androidx.core_core-ktx",
+ "androidx.lifecycle_lifecycle-extensions",
+ "androidx.lifecycle_lifecycle-livedata",
+ "androidx.lifecycle_lifecycle-runtime-ktx",
+ "androidx.lifecycle_lifecycle-viewmodel-compose",
+ "androidx.navigation_navigation-compose",
+ "androidx.recyclerview_recyclerview",
+ ],
+
+ platform_apis: true,
+
+ kotlincflags: ["-Xjvm-default=enable"],
+}
diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml
new file mode 100644
index 0000000..586ef86
--- /dev/null
+++ b/packages/CredentialManager/AndroidManifest.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2017 Google Inc.
+ *
+ * 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.credentialmanager">
+
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+ <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/>
+
+ <application
+ android:allowBackup="true"
+ android:dataExtractionRules="@xml/data_extraction_rules"
+ android:fullBackupContent="@xml/backup_rules"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.CredentialSelector">
+
+ <activity
+ android:name=".CredentialSelectorActivity"
+ android:exported="true"
+ android:label="@string/app_name"
+ android:launchMode="singleInstance"
+ android:noHistory="true"
+ android:excludeFromRecents="true"
+ android:theme="@style/Theme.CredentialSelector">
+ </activity>
+ </application>
+
+</manifest>
diff --git a/packages/CredentialManager/res/drawable-v24/ic_launcher_foreground.xml b/packages/CredentialManager/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..966abaf
--- /dev/null
+++ b/packages/CredentialManager/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportHeight="108"
+ android:viewportWidth="108">
+ <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:endX="85.84757"
+ android:endY="92.4963"
+ android:startX="42.9492"
+ android:startY="49.59793"
+ android:type="linear">
+ <item
+ android:color="#44000000"
+ android:offset="0.0" />
+ <item
+ android:color="#00000000"
+ android:offset="1.0" />
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:fillColor="#FFFFFF"
+ android:fillType="nonZero"
+ android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+ android:strokeColor="#00000000"
+ android:strokeWidth="1" />
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_launcher_background.xml b/packages/CredentialManager/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..61bb79e
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportHeight="108"
+ android:viewportWidth="108">
+ <path
+ android:fillColor="#3DDC84"
+ android:pathData="M0,0h108v108h-108z" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M9,0L9,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,0L19,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,0L29,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,0L39,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,0L49,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,0L59,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,0L69,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,0L79,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M89,0L89,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M99,0L99,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,9L108,9"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,19L108,19"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,29L108,29"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,39L108,39"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,49L108,49"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,59L108,59"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,69L108,69"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,79L108,79"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,89L108,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,99L108,99"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,29L89,29"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,39L89,39"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,49L89,49"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,59L89,59"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,69L89,69"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,79L89,79"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,19L29,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,19L39,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,19L49,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,19L59,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,19L69,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,19L79,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+</vector>
diff --git a/packages/CredentialManager/res/drawable/ic_passkey.xml b/packages/CredentialManager/res/drawable/ic_passkey.xml
new file mode 100644
index 0000000..041a321
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkey.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="28dp"
+ android:height="24dp"
+ android:viewportWidth="28"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M27.453,13.253C27.453,14.952 26.424,16.411 24.955,17.041L26.21,18.295L24.839,19.666L26.21,21.037L23.305,23.942L22.012,22.65L22.012,17.156C20.385,16.605 19.213,15.066 19.213,13.253C19.213,10.977 21.058,9.133 23.333,9.133C25.609,9.133 27.453,10.977 27.453,13.253ZM25.47,13.254C25.47,14.434 24.514,15.39 23.334,15.39C22.154,15.39 21.197,14.434 21.197,13.254C21.197,12.074 22.154,11.118 23.334,11.118C24.514,11.118 25.47,12.074 25.47,13.254Z"
+ android:fillColor="#00639B"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M17.85,5.768C17.85,8.953 15.268,11.536 12.083,11.536C8.897,11.536 6.315,8.953 6.315,5.768C6.315,2.582 8.897,0 12.083,0C15.268,0 17.85,2.582 17.85,5.768Z"
+ android:fillColor="#00639B"/>
+ <path
+ android:pathData="M0.547,20.15C0.547,16.32 8.23,14.382 12.083,14.382C13.59,14.382 15.684,14.679 17.674,15.269C18.116,16.454 18.952,17.447 20.022,18.089V23.071H0.547V20.15Z"
+ android:fillColor="#00639B"/>
+</vector>
diff --git a/packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher.xml b/packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..03eed25
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher_round.xml b/packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..03eed25
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/mipmap-hdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/CredentialManager/res/mipmap-hdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/CredentialManager/res/mipmap-mdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/CredentialManager/res/mipmap-mdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/CredentialManager/res/mipmap-xhdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/CredentialManager/res/mipmap-xhdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f508
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml
new file mode 100644
index 0000000..09837df62
--- /dev/null
+++ b/packages/CredentialManager/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="purple_200">#FFBB86FC</color>
+ <color name="purple_500">#FF6200EE</color>
+ <color name="purple_700">#FF3700B3</color>
+ <color name="teal_200">#FF03DAC5</color>
+ <color name="teal_700">#FF018786</color>
+ <color name="black">#FF000000</color>
+ <color name="white">#FFFFFFFF</color>
+</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
new file mode 100644
index 0000000..2901705
--- /dev/null
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -0,0 +1,13 @@
+<resources>
+ <string name="app_name">CredentialManager</string>
+ <string name="string_cancel">Cancel</string>
+ <string name="string_continue">Continue</string>
+ <string name="string_more_options">More options</string>
+ <string name="string_no_thanks">No thanks</string>
+ <string name="passkey_creation_intro_title">A simple way to sign in safely</string>
+ <string name="passkey_creation_intro_body">Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more</string>
+ <string name="choose_provider_title">Choose your default provider</string>
+ <string name="choose_provider_body">This provider will store passkeys and passwords for you and help you easily autofill and sign in. Learn more</string>
+ <string name="choose_create_option_title">Create a passkey at</string>
+ <string name="choose_sign_in_title">Use saved sign in</string>
+</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/themes.xml b/packages/CredentialManager/res/values/themes.xml
new file mode 100644
index 0000000..feec746
--- /dev/null
+++ b/packages/CredentialManager/res/values/themes.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <style name="Theme.CredentialSelector" parent="@android:style/ThemeOverlay.Material">
+ <item name="android:statusBarColor">@color/purple_700</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:colorBackgroundCacheHint">@null</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/xml/backup_rules.xml b/packages/CredentialManager/res/xml/backup_rules.xml
new file mode 100644
index 0000000..9b42d90
--- /dev/null
+++ b/packages/CredentialManager/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Sample backup rules file; uncomment and customize as necessary.
+ See https://developer.android.com/guide/topics/data/autobackup
+ for details.
+ Note: This file is ignored for devices older that API 31
+ See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+ <!--
+ <include domain="sharedpref" path="."/>
+ <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/xml/data_extraction_rules.xml b/packages/CredentialManager/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..c6c3bb0
--- /dev/null
+++ b/packages/CredentialManager/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Sample data extraction rules file; uncomment and customize as necessary.
+ See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+ for details.
+-->
+<data-extraction-rules>
+ <cloud-backup>
+ <!-- TODO: Use <include> and <exclude> to control what is backed up.
+ <include .../>
+ <exclude .../>
+ -->
+ </cloud-backup>
+ <!--
+ <device-transfer>
+ <include .../>
+ <exclude .../>
+ </device-transfer>
+ -->
+</data-extraction-rules>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
new file mode 100644
index 0000000..f20104a
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -0,0 +1,131 @@
+package com.android.credentialmanager
+
+import android.content.Context
+import com.android.credentialmanager.createflow.CreateOptionInfo
+import com.android.credentialmanager.createflow.ProviderInfo
+import com.android.credentialmanager.createflow.ProviderList
+import com.android.credentialmanager.getflow.CredentialOptionInfo
+
+class CredentialManagerRepo(
+ private val context: Context
+) {
+ fun getCredentialProviderList(): List<com.android.credentialmanager.getflow.ProviderInfo> {
+ return listOf(
+ com.android.credentialmanager.getflow.ProviderInfo(
+ icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ name = "Google Password Manager",
+ appDomainName = "tribank.us",
+ credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+ credentialOptions = listOf(
+ CredentialOptionInfo(
+ icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ title = "Elisa Backett",
+ subtitle = "elisa.beckett@gmail.com",
+ id = "id-1",
+ ),
+ CredentialOptionInfo(
+ icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ title = "Elisa Backett Work",
+ subtitle = "elisa.beckett.work@google.com",
+ id = "id-2",
+ ),
+ )
+ ),
+ com.android.credentialmanager.getflow.ProviderInfo(
+ icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ name = "Lastpass",
+ appDomainName = "tribank.us",
+ credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+ credentialOptions = listOf(
+ CredentialOptionInfo(
+ icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ title = "Elisa Backett",
+ subtitle = "elisa.beckett@lastpass.com",
+ id = "id-1",
+ ),
+ )
+ ),
+ com.android.credentialmanager.getflow.ProviderInfo(
+ icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ name = "Dashlane",
+ appDomainName = "tribank.us",
+ credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+ credentialOptions = listOf(
+ CredentialOptionInfo(
+ icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ title = "Elisa Backett",
+ subtitle = "elisa.beckett@dashlane.com",
+ id = "id-1",
+ ),
+ )
+ ),
+ )
+ }
+
+ fun createCredentialProviderList(): ProviderList {
+ return ProviderList(
+ listOf(
+ ProviderInfo(
+ icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ name = "Google Password Manager",
+ appDomainName = "tribank.us",
+ credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+ createOptions = listOf(
+ CreateOptionInfo(
+ icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ title = "Elisa Backett",
+ subtitle = "elisa.beckett@gmail.com",
+ id = "id-1",
+ ),
+ CreateOptionInfo(
+ icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ title = "Elisa Backett Work",
+ subtitle = "elisa.beckett.work@google.com",
+ id = "id-2",
+ ),
+ )
+ ),
+ ProviderInfo(
+ icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ name = "Lastpass",
+ appDomainName = "tribank.us",
+ credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+ createOptions = listOf(
+ CreateOptionInfo(
+ icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ title = "Elisa Backett",
+ subtitle = "elisa.beckett@lastpass.com",
+ id = "id-1",
+ ),
+ )
+ ),
+ ProviderInfo(
+ icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ name = "Dashlane",
+ appDomainName = "tribank.us",
+ credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+ createOptions = listOf(
+ CreateOptionInfo(
+ icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ title = "Elisa Backett",
+ subtitle = "elisa.beckett@dashlane.com",
+ id = "id-1",
+ ),
+ )
+ ),
+ )
+ )
+ }
+
+ companion object {
+ lateinit var repo: CredentialManagerRepo
+
+ fun setup(context: Context) {
+ repo = CredentialManagerRepo(context)
+ }
+
+ fun getInstance(): CredentialManagerRepo {
+ return repo
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
new file mode 100644
index 0000000..dd4ba11
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -0,0 +1,63 @@
+package com.android.credentialmanager
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.rememberNavController
+import com.android.credentialmanager.createflow.CreatePasskeyViewModel
+import com.android.credentialmanager.createflow.createPasskeyGraph
+import com.android.credentialmanager.getflow.GetCredentialViewModel
+import com.android.credentialmanager.getflow.getCredentialsGraph
+import com.android.credentialmanager.ui.theme.CredentialSelectorTheme
+
+@ExperimentalMaterialApi
+class CredentialSelectorActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ CredentialManagerRepo.setup(this)
+ val startDestination = intent.extras?.getString(
+ "start_destination",
+ "getCredentials"
+ ) ?: "getCredentials"
+
+ setContent {
+ CredentialSelectorTheme {
+ AppNavHost(
+ startDestination = startDestination,
+ onCancel = {this.finish()}
+ )
+ }
+ }
+ }
+
+ @ExperimentalMaterialApi
+ @Composable
+ fun AppNavHost(
+ modifier: Modifier = Modifier,
+ navController: NavHostController = rememberNavController(),
+ startDestination: String,
+ onCancel: () -> Unit,
+ ) {
+ NavHost(
+ modifier = modifier,
+ navController = navController,
+ startDestination = startDestination
+ ) {
+ createPasskeyGraph(
+ navController = navController,
+ viewModel = CreatePasskeyViewModel(CredentialManagerRepo.repo),
+ onCancel = onCancel
+ )
+ getCredentialsGraph(
+ navController = navController,
+ viewModel = GetCredentialViewModel(CredentialManagerRepo.repo),
+ onCancel = onCancel
+ )
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
new file mode 100644
index 0000000..62c244c
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -0,0 +1,22 @@
+package com.android.credentialmanager.createflow
+
+import android.graphics.drawable.Drawable
+
+data class ProviderInfo(
+ val icon: Drawable,
+ val name: String,
+ val appDomainName: String,
+ val credentialTypeIcon: Drawable,
+ val createOptions: List<CreateOptionInfo>,
+)
+
+data class ProviderList(
+ val providers: List<ProviderInfo>,
+)
+
+data class CreateOptionInfo(
+ val icon: Drawable,
+ val title: String,
+ val subtitle: String,
+ val id: String,
+)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
new file mode 100644
index 0000000..fb6db21
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -0,0 +1,541 @@
+package com.android.credentialmanager.createflow
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonColors
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.Card
+import androidx.compose.material.Chip
+import androidx.compose.material.ChipDefaults
+import androidx.compose.material.Divider
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.Icon
+import androidx.compose.material.ModalBottomSheetLayout
+import androidx.compose.material.ModalBottomSheetValue
+import androidx.compose.material.Text
+import androidx.compose.material.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.core.graphics.drawable.toBitmap
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavType
+import androidx.navigation.compose.composable
+import androidx.navigation.navArgument
+import androidx.navigation.navigation
+import com.android.credentialmanager.R
+import com.android.credentialmanager.ui.theme.Grey100
+import com.android.credentialmanager.ui.theme.Shapes
+import com.android.credentialmanager.ui.theme.Typography
+import com.android.credentialmanager.ui.theme.lightBackgroundColor
+import com.android.credentialmanager.ui.theme.lightColorAccentSecondary
+import com.android.credentialmanager.ui.theme.lightSurface1
+
+@ExperimentalMaterialApi
+fun NavGraphBuilder.createPasskeyGraph(
+ navController: NavController,
+ viewModel: CreatePasskeyViewModel,
+ onCancel: () -> Unit,
+ startDestination: String = "intro", // TODO: get this from view model
+) {
+ navigation(startDestination = startDestination, route = "createPasskey") {
+ composable("intro") {
+ CreatePasskeyIntroDialog(
+ onCancel = onCancel,
+ onConfirm = {viewModel.onConfirm(navController)}
+ )
+ }
+ composable("providerSelection") {
+ ProviderSelectionDialog(
+ providerList = viewModel.uiState.collectAsState().value.providerList,
+ onProviderSelected = {viewModel.onProviderSelected(it, navController)},
+ onCancel = onCancel
+ )
+ }
+ composable(
+ "createCredentialSelection/{providerName}",
+ arguments = listOf(navArgument("providerName") {type = NavType.StringType})
+ ) {
+ val arguments = it.arguments
+ if (arguments == null) {
+ throw java.lang.IllegalStateException("createCredentialSelection without a provider name")
+ }
+ CreationSelectionDialog(
+ providerInfo = viewModel.getProviderInfoByName(arguments.getString("providerName")!!),
+ onOptionSelected = {viewModel.onCreateOptionSelected(it)},
+ onCancel = onCancel,
+ multiProvider = viewModel.uiState.collectAsState().value.providerList.providers.size > 1,
+ onMoreOptionSelected = {viewModel.onMoreOptionSelected(navController)},
+ )
+ }
+ }
+}
+
+/**
+ * BEGIN INTRO CONTENT
+ */
+@ExperimentalMaterialApi
+@Composable
+fun CreatePasskeyIntroDialog(
+ onCancel: () -> Unit = {},
+ onConfirm: () -> Unit = {},
+) {
+ val state = rememberModalBottomSheetState(
+ initialValue = ModalBottomSheetValue.Expanded,
+ skipHalfExpanded = true
+ )
+ ModalBottomSheetLayout(
+ sheetState = state,
+ sheetContent = {
+ ConfirmationCard(
+ onCancel = onCancel, onConfirm = onConfirm
+ )
+ },
+ scrimColor = Color.Transparent,
+ sheetShape = Shapes.medium,
+ ) {}
+ LaunchedEffect(state.currentValue) {
+ when (state.currentValue) {
+ ModalBottomSheetValue.Hidden -> {
+ onCancel()
+ }
+ }
+ }
+}
+
+@Composable
+fun ConfirmationCard(
+ onConfirm: () -> Unit,
+ onCancel: () -> Unit,
+) {
+ Card(
+ backgroundColor = lightBackgroundColor,
+ ) {
+ Column() {
+ Icon(
+ painter = painterResource(R.drawable.ic_passkey),
+ contentDescription = null,
+ tint = Color.Unspecified,
+ modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp)
+ )
+ Text(
+ text = stringResource(R.string.passkey_creation_intro_title),
+ style = Typography.subtitle1,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally)
+ )
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Text(
+ text = stringResource(R.string.passkey_creation_intro_body),
+ style = Typography.body1,
+ modifier = Modifier.padding(horizontal = 28.dp)
+ )
+ Divider(
+ thickness = 48.dp,
+ color = Color.Transparent
+ )
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ CancelButton(
+ stringResource(R.string.string_cancel),
+ onclick = onCancel
+ )
+ ConfirmButton(
+ stringResource(R.string.string_continue),
+ onclick = onConfirm
+ )
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ }
+}
+
+/**
+ * END INTRO CONTENT
+ */
+
+/**
+ * BEGIN PROVIDER SELECTION CONTENT
+ */
+@ExperimentalMaterialApi
+@Composable
+fun ProviderSelectionDialog(
+ providerList: ProviderList,
+ onProviderSelected: (String) -> Unit,
+ onCancel: () -> Unit,
+) {
+ val state = rememberModalBottomSheetState(
+ initialValue = ModalBottomSheetValue.Expanded,
+ skipHalfExpanded = true
+ )
+ ModalBottomSheetLayout(
+ sheetState = state,
+ sheetContent = {
+ ProviderSelectionCard(
+ providerList = providerList,
+ onCancel = onCancel,
+ onProviderSelected = onProviderSelected
+ )
+ },
+ scrimColor = Color.Transparent,
+ sheetShape = Shapes.medium,
+ ) {}
+ LaunchedEffect(state.currentValue) {
+ when (state.currentValue) {
+ ModalBottomSheetValue.Hidden -> {
+ onCancel()
+ }
+ }
+ }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun ProviderSelectionCard(
+ providerList: ProviderList,
+ onProviderSelected: (String) -> Unit,
+ onCancel: () -> Unit
+) {
+ Card(
+ backgroundColor = lightBackgroundColor,
+ ) {
+ Column() {
+ Text(
+ text = stringResource(R.string.choose_provider_title),
+ style = Typography.subtitle1,
+ modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
+ )
+ Text(
+ text = stringResource(R.string.choose_provider_body),
+ style = Typography.body1,
+ modifier = Modifier.padding(horizontal = 28.dp)
+ )
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Card(
+ shape = Shapes.medium,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally)
+ ) {
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(2.dp)
+ ) {
+ providerList.providers.forEach {
+ item {
+ ProviderRow(providerInfo = it, onProviderSelected = onProviderSelected)
+ }
+ }
+ }
+ }
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Row(
+ horizontalArrangement = Arrangement.Start,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ CancelButton(stringResource(R.string.string_cancel), onCancel)
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun ProviderRow(providerInfo: ProviderInfo, onProviderSelected: (String) -> Unit) {
+ Chip(
+ modifier = Modifier.fillMaxWidth(),
+ onClick = {onProviderSelected(providerInfo.name)},
+ leadingIcon = {
+ Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+ bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
+ // painter = painterResource(R.drawable.ic_passkey),
+ // TODO: add description.
+ contentDescription = "")
+ },
+ colors = ChipDefaults.chipColors(
+ backgroundColor = Grey100,
+ leadingIconContentColor = Grey100
+ ),
+ shape = Shapes.large
+ ) {
+ Text(
+ text = providerInfo.name,
+ style = Typography.button,
+ modifier = Modifier.padding(vertical = 18.dp)
+ )
+ }
+}
+
+/**
+ * END PROVIDER SELECTION CONTENT
+ */
+
+/**
+ * BEGIN COMMON COMPONENTS
+ */
+
+@Composable
+fun CancelButton(text: String, onclick: () -> Unit) {
+ val colors = ButtonDefaults.buttonColors(
+ backgroundColor = lightBackgroundColor
+ )
+ NavigationButton(
+ border = BorderStroke(1.dp, lightSurface1),
+ colors = colors,
+ text = text,
+ onclick = onclick)
+}
+
+@Composable
+fun ConfirmButton(text: String, onclick: () -> Unit) {
+ val colors = ButtonDefaults.buttonColors(
+ backgroundColor = lightColorAccentSecondary
+ )
+ NavigationButton(
+ colors = colors,
+ text = text,
+ onclick = onclick)
+}
+
+@Composable
+fun NavigationButton(
+ border: BorderStroke? = null,
+ colors: ButtonColors,
+ text: String,
+ onclick: () -> Unit
+) {
+ Button(
+ onClick = onclick,
+ shape = Shapes.small,
+ colors = colors,
+ border = border
+ ) {
+ Text(text = text, style = Typography.button)
+ }
+}
+
+/**
+ * BEGIN CREATE OPTION SELECTION CONTENT
+ */
+@ExperimentalMaterialApi
+@Composable
+fun CreationSelectionDialog(
+ providerInfo: ProviderInfo,
+ onOptionSelected: (String) -> Unit,
+ onCancel: () -> Unit,
+ multiProvider: Boolean,
+ onMoreOptionSelected: () -> Unit,
+) {
+ val state = rememberModalBottomSheetState(
+ initialValue = ModalBottomSheetValue.Expanded,
+ skipHalfExpanded = true
+ )
+ ModalBottomSheetLayout(
+ sheetState = state,
+ sheetContent = {
+ CreationSelectionCard(
+ providerInfo = providerInfo,
+ onCancel = onCancel,
+ onOptionSelected = onOptionSelected,
+ multiProvider = multiProvider,
+ onMoreOptionSelected = onMoreOptionSelected,
+ )
+ },
+ scrimColor = Color.Transparent,
+ sheetShape = Shapes.medium,
+ ) {}
+ LaunchedEffect(state.currentValue) {
+ when (state.currentValue) {
+ ModalBottomSheetValue.Hidden -> {
+ onCancel()
+ }
+ }
+ }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun CreationSelectionCard(
+ providerInfo: ProviderInfo,
+ onOptionSelected: (String) -> Unit,
+ onCancel: () -> Unit,
+ multiProvider: Boolean,
+ onMoreOptionSelected: () -> Unit,
+) {
+ Card(
+ backgroundColor = lightBackgroundColor,
+ ) {
+ Column() {
+ Icon(
+ bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
+ contentDescription = null,
+ tint = Color.Unspecified,
+ modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp)
+ )
+ Text(
+ text = "${stringResource(R.string.choose_create_option_title)} ${providerInfo.name}",
+ style = Typography.subtitle1,
+ modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
+ )
+ Text(
+ text = providerInfo.appDomainName,
+ style = Typography.body2,
+ modifier = Modifier.padding(horizontal = 28.dp)
+ )
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Card(
+ shape = Shapes.medium,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally)
+ ) {
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(2.dp)
+ ) {
+ providerInfo.createOptions.forEach {
+ item {
+ CreateOptionRow(createOptionInfo = it, onOptionSelected = onOptionSelected)
+ }
+ }
+ if (multiProvider) {
+ item {
+ MoreOptionRow(onSelect = onMoreOptionSelected)
+ }
+ }
+ }
+ }
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Row(
+ horizontalArrangement = Arrangement.Start,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ CancelButton(stringResource(R.string.string_cancel), onCancel)
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun CreateOptionRow(createOptionInfo: CreateOptionInfo, onOptionSelected: (String) -> Unit) {
+ Chip(
+ modifier = Modifier.fillMaxWidth(),
+ onClick = {onOptionSelected(createOptionInfo.id)},
+ leadingIcon = {
+ Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+ bitmap = createOptionInfo.icon.toBitmap().asImageBitmap(),
+ // painter = painterResource(R.drawable.ic_passkey),
+ // TODO: add description.
+ contentDescription = "")
+ },
+ colors = ChipDefaults.chipColors(
+ backgroundColor = Grey100,
+ leadingIconContentColor = Grey100
+ ),
+ shape = Shapes.large
+ ) {
+ Column() {
+ Text(
+ text = createOptionInfo.title,
+ style = Typography.h6,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ Text(
+ text = createOptionInfo.subtitle,
+ style = Typography.body2,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun MoreOptionRow(onSelect: () -> Unit) {
+ Chip(
+ modifier = Modifier.fillMaxWidth().height(52.dp),
+ onClick = onSelect,
+ colors = ChipDefaults.chipColors(
+ backgroundColor = Grey100,
+ leadingIconContentColor = Grey100
+ ),
+ shape = Shapes.large
+ ) {
+ Text(
+ text = stringResource(R.string.string_more_options),
+ style = Typography.h6,
+ )
+ }
+}
+/**
+ * END CREATE OPTION SELECTION CONTENT
+ */
+
+/**
+ * END COMMON COMPONENTS
+ */
+
+@ExperimentalMaterialApi
+@Preview(showBackground = true)
+@Composable
+fun CreatePasskeyEntryScreenPreview() {
+ // val providers = ProviderList(
+ // listOf(
+ // ProviderInfo(null),
+ // ProviderInfo(null, "Dashlane"),
+ // ProviderInfo(null, "LastPass")
+ // )
+ // )
+ // TatiAccountSelectorTheme {
+ // ConfirmationCard({}, {})
+ // }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
new file mode 100644
index 0000000..3554285
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
@@ -0,0 +1,51 @@
+package com.android.credentialmanager.createflow
+
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.navigation.NavController
+import com.android.credentialmanager.CredentialManagerRepo
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+data class CreatePasskeyUiState(
+ val providerList: ProviderList,
+)
+
+class CreatePasskeyViewModel(
+ credManRepo: CredentialManagerRepo
+) : ViewModel() {
+
+ private val _uiState = MutableStateFlow(
+ CreatePasskeyUiState(credManRepo.createCredentialProviderList())
+ )
+ val uiState: StateFlow<CreatePasskeyUiState> = _uiState.asStateFlow()
+
+ fun onConfirm(navController: NavController) {
+ if (uiState.value.providerList.providers.size > 1) {
+ navController.navigate("providerSelection")
+ } else if (uiState.value.providerList.providers.size == 1) {
+ onProviderSelected(uiState.value.providerList.providers[0].name, navController)
+ } else {
+ throw java.lang.IllegalStateException("Empty provider list.")
+ }
+ }
+
+ fun onProviderSelected(providerName: String, navController: NavController) {
+ return navController.navigate("createCredentialSelection/$providerName")
+ }
+
+ fun onCreateOptionSelected(createOptionId: String) {
+ Log.d("Account Selector", "Option selected for creation: $createOptionId")
+ }
+
+ fun getProviderInfoByName(providerName: String): ProviderInfo {
+ return uiState.value.providerList.providers.single {
+ it.name.equals(providerName)
+ }
+ }
+
+ fun onMoreOptionSelected(navController: NavController) {
+ navController.navigate("moreOption")
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
new file mode 100644
index 0000000..6ad14db
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -0,0 +1,232 @@
+package com.android.credentialmanager.getflow
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.Card
+import androidx.compose.material.Chip
+import androidx.compose.material.ChipDefaults
+import androidx.compose.material.Divider
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.Icon
+import androidx.compose.material.ModalBottomSheetLayout
+import androidx.compose.material.ModalBottomSheetValue
+import androidx.compose.material.Text
+import androidx.compose.material.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.core.graphics.drawable.toBitmap
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.compose.composable
+import androidx.navigation.navigation
+import com.android.credentialmanager.R
+import com.android.credentialmanager.createflow.CancelButton
+import com.android.credentialmanager.ui.theme.Grey100
+import com.android.credentialmanager.ui.theme.Shapes
+import com.android.credentialmanager.ui.theme.Typography
+import com.android.credentialmanager.ui.theme.lightBackgroundColor
+
+@ExperimentalMaterialApi
+fun NavGraphBuilder.getCredentialsGraph(
+ navController: NavController,
+ viewModel: GetCredentialViewModel,
+ onCancel: () -> Unit,
+ startDestination: String = "credentialSelection", // TODO: get this from view model
+) {
+ navigation(startDestination = startDestination, route = "getCredentials") {
+ composable("credentialSelection") {
+ CredentialSelectionDialog(
+ providerInfo = viewModel.getDefaultProviderInfo(),
+ onOptionSelected = {viewModel.onCredentailSelected(it, navController)},
+ onCancel = onCancel,
+ multiProvider = viewModel.uiState.collectAsState().value.providers.size > 1,
+ onMoreOptionSelected = {viewModel.onMoreOptionSelected(navController)}
+ )
+ }
+ }
+}
+
+/**
+ * BEGIN CREATE OPTION SELECTION CONTENT
+ */
+@ExperimentalMaterialApi
+@Composable
+fun CredentialSelectionDialog(
+ providerInfo: ProviderInfo,
+ onOptionSelected: (String) -> Unit,
+ onCancel: () -> Unit,
+ multiProvider: Boolean,
+ onMoreOptionSelected: () -> Unit,
+) {
+ val state = rememberModalBottomSheetState(
+ initialValue = ModalBottomSheetValue.Expanded,
+ skipHalfExpanded = true
+ )
+ ModalBottomSheetLayout(
+ sheetState = state,
+ sheetContent = {
+ CredentialSelectionCard(
+ providerInfo = providerInfo,
+ onCancel = onCancel,
+ onOptionSelected = onOptionSelected,
+ multiProvider = multiProvider,
+ onMoreOptionSelected = onMoreOptionSelected,
+ )
+ },
+ scrimColor = Color.Transparent,
+ sheetShape = Shapes.medium,
+ ) {}
+ LaunchedEffect(state.currentValue) {
+ when (state.currentValue) {
+ ModalBottomSheetValue.Hidden -> {
+ onCancel()
+ }
+ }
+ }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun CredentialSelectionCard(
+ providerInfo: ProviderInfo,
+ onOptionSelected: (String) -> Unit,
+ onCancel: () -> Unit,
+ multiProvider: Boolean,
+ onMoreOptionSelected: () -> Unit,
+) {
+ Card(
+ backgroundColor = lightBackgroundColor,
+ ) {
+ Column() {
+ Icon(
+ bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
+ contentDescription = null,
+ tint = Color.Unspecified,
+ modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp)
+ )
+ Text(
+ text = stringResource(R.string.choose_sign_in_title),
+ style = Typography.subtitle1,
+ modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
+ )
+ Text(
+ text = providerInfo.appDomainName,
+ style = Typography.body2,
+ modifier = Modifier.padding(horizontal = 28.dp)
+ )
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Card(
+ shape = Shapes.medium,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally)
+ ) {
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(2.dp)
+ ) {
+ providerInfo.credentialOptions.forEach {
+ item {
+ CredentialOptionRow(credentialOptionInfo = it, onOptionSelected = onOptionSelected)
+ }
+ }
+ if (multiProvider) {
+ item {
+ MoreOptionRow(onSelect = onMoreOptionSelected)
+ }
+ }
+ }
+ }
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Row(
+ horizontalArrangement = Arrangement.Start,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ CancelButton(stringResource(R.string.string_no_thanks), onCancel)
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun CredentialOptionRow(
+ credentialOptionInfo: CredentialOptionInfo,
+ onOptionSelected: (String) -> Unit
+) {
+ Chip(
+ modifier = Modifier.fillMaxWidth(),
+ onClick = {onOptionSelected(credentialOptionInfo.id)},
+ leadingIcon = {
+ Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+ bitmap = credentialOptionInfo.icon.toBitmap().asImageBitmap(),
+ // painter = painterResource(R.drawable.ic_passkey),
+ // TODO: add description.
+ contentDescription = "")
+ },
+ colors = ChipDefaults.chipColors(
+ backgroundColor = Grey100,
+ leadingIconContentColor = Grey100
+ ),
+ shape = Shapes.large
+ ) {
+ Column() {
+ Text(
+ text = credentialOptionInfo.title,
+ style = Typography.h6,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ Text(
+ text = credentialOptionInfo.subtitle,
+ style = Typography.body2,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun MoreOptionRow(onSelect: () -> Unit) {
+ Chip(
+ modifier = Modifier.fillMaxWidth().height(52.dp),
+ onClick = onSelect,
+ colors = ChipDefaults.chipColors(
+ backgroundColor = Grey100,
+ leadingIconContentColor = Grey100
+ ),
+ shape = Shapes.large
+ ) {
+ Text(
+ text = stringResource(R.string.string_more_options),
+ style = Typography.h6,
+ )
+ }
+}
+/**
+ * END CREATE OPTION SELECTION CONTENT
+ */
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
new file mode 100644
index 0000000..20057de
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -0,0 +1,36 @@
+package com.android.credentialmanager.getflow
+
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.navigation.NavController
+import com.android.credentialmanager.CredentialManagerRepo
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+data class GetCredentialUiState(
+ val providers: List<ProviderInfo>
+)
+
+class GetCredentialViewModel(
+ credManRepo: CredentialManagerRepo
+) : ViewModel() {
+
+ private val _uiState = MutableStateFlow(
+ GetCredentialUiState(credManRepo.getCredentialProviderList())
+ )
+ val uiState: StateFlow<GetCredentialUiState> = _uiState.asStateFlow()
+
+ fun getDefaultProviderInfo(): ProviderInfo {
+ // TODO: correctly get the default provider.
+ return uiState.value.providers.first()
+ }
+
+ fun onCredentailSelected(credentialId: String, navController: NavController) {
+ Log.d("Account Selector", "credential selected: $credentialId")
+ }
+
+ fun onMoreOptionSelected(navController: NavController) {
+ Log.d("Account Selector", "More Option selected")
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
new file mode 100644
index 0000000..8710ece
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -0,0 +1,18 @@
+package com.android.credentialmanager.getflow
+
+import android.graphics.drawable.Drawable
+
+data class ProviderInfo(
+ val icon: Drawable,
+ val name: String,
+ val appDomainName: String,
+ val credentialTypeIcon: Drawable,
+ val credentialOptions: List<CredentialOptionInfo>,
+)
+
+data class CredentialOptionInfo(
+ val icon: Drawable,
+ val title: String,
+ val subtitle: String,
+ val id: String,
+)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt
new file mode 100644
index 0000000..abb4bfb
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt
@@ -0,0 +1,14 @@
+package com.android.credentialmanager.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Grey100 = Color(0xFFF1F3F4)
+val Purple200 = Color(0xFFBB86FC)
+val Purple500 = Color(0xFF6200EE)
+val Purple700 = Color(0xFF3700B3)
+val Teal200 = Color(0xFF03DAC5)
+val lightColorAccentSecondary = Color(0xFFC2E7FF)
+val lightBackgroundColor = Color(0xFFF0F0F0)
+val lightSurface1 = Color(0xFF6991D6)
+val textColorSecondary = Color(0xFF40484B)
+val textColorPrimary = Color(0xFF191C1D)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
new file mode 100644
index 0000000..cba8658
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
@@ -0,0 +1,11 @@
+package com.android.credentialmanager.ui.theme
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Shapes
+import androidx.compose.ui.unit.dp
+
+val Shapes = Shapes(
+ small = RoundedCornerShape(100.dp),
+ medium = RoundedCornerShape(20.dp),
+ large = RoundedCornerShape(0.dp)
+)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
new file mode 100644
index 0000000..a9d20ae
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
@@ -0,0 +1,47 @@
+package com.android.credentialmanager.ui.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.darkColors
+import androidx.compose.material.lightColors
+import androidx.compose.runtime.Composable
+
+private val DarkColorPalette = darkColors(
+ primary = Purple200,
+ primaryVariant = Purple700,
+ secondary = Teal200
+)
+
+private val LightColorPalette = lightColors(
+ primary = Purple500,
+ primaryVariant = Purple700,
+ secondary = Teal200
+
+ /* Other default colors to override
+ background = Color.White,
+ surface = Color.White,
+ onPrimary = Color.White,
+ onSecondary = Color.Black,
+ onBackground = Color.Black,
+ onSurface = Color.Black,
+ */
+)
+
+@Composable
+fun CredentialSelectorTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ content: @Composable () -> Unit
+) {
+ val colors = if (darkTheme) {
+ DarkColorPalette
+ } else {
+ LightColorPalette
+ }
+
+ MaterialTheme(
+ colors = colors,
+ typography = Typography,
+ shapes = Shapes,
+ content = content
+ )
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt
new file mode 100644
index 0000000..d8fb01c
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt
@@ -0,0 +1,56 @@
+package com.android.credentialmanager.ui.theme
+
+import androidx.compose.material.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+ subtitle1 = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 24.sp,
+ lineHeight = 32.sp,
+ ),
+ body1 = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ ),
+ body2 = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ color = textColorSecondary
+ ),
+ button = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ ),
+ h6 = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ color = textColorPrimary
+ ),
+
+ /* Other default text styles to override
+ button = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.W500,
+ fontSize = 14.sp
+ ),
+ caption = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 12.sp
+ )
+ */
+)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt
index 773e5f3..6fe88e1 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt
@@ -54,7 +54,7 @@
parameter: List<NamedNavArgument> = emptyList(),
arguments: Bundle? = null
): SettingsPage {
- return SettingsPage(
+ return SettingsPage.create(
name = SppName.name,
displayName = SppName.displayName,
parameter = parameter,
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
index c8a4650..c68e918 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
@@ -18,14 +18,18 @@
import android.os.Bundle
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.getRuntimeArguments
+import com.android.settingslib.spa.framework.util.mergeArguments
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
+import com.android.settingslib.spa.gallery.createSettingsPage
import com.android.settingslib.spa.gallery.page.ArgumentPageModel
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
import com.android.settingslib.spa.gallery.page.FooterPageProvider
@@ -41,27 +45,36 @@
override val name = SettingsPageProviderEnum.HOME.name
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val owner = createSettingsPage(SettingsPageProviderEnum.HOME)
return listOf(
- PreferenceMainPageProvider.buildInjectEntry().build(),
- ArgumentPageProvider.buildInjectEntry("foo")!!.build(),
- SliderPageProvider.buildInjectEntry().build(),
- SpinnerPageProvider.buildInjectEntry().build(),
- SettingsPagerPageProvider.buildInjectEntry().build(),
- FooterPageProvider.buildInjectEntry().build(),
- IllustrationPageProvider.buildInjectEntry().build(),
- CategoryPageProvider.buildInjectEntry().build(),
- ActionButtonPageProvider.buildInjectEntry().build(),
+ PreferenceMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(),
+ SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ SettingsPagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ FooterPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ IllustrationPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
)
}
@Composable
override fun Page(arguments: Bundle?) {
+ val globalRuntimeArgs = remember { getRuntimeArguments(arguments) }
HomeScaffold(title = stringResource(R.string.app_name)) {
for (entry in buildEntry(arguments)) {
if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) {
- entry.UiLayout(ArgumentPageModel.buildArgument(intParam = 0))
+ entry.UiLayout(
+ mergeArguments(
+ listOf(
+ globalRuntimeArgs,
+ ArgumentPageModel.buildArgument(intParam = 0)
+ )
+ )
+ )
} else {
- entry.UiLayout()
+ entry.UiLayout(globalRuntimeArgs)
}
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index cc0a79a..9bf7ad8 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -18,12 +18,15 @@
import android.os.Bundle
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.getRuntimeArguments
+import com.android.settingslib.spa.framework.util.mergeArguments
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
import com.android.settingslib.spa.gallery.createSettingsPage
import com.android.settingslib.spa.widget.preference.Preference
@@ -98,12 +101,20 @@
@Composable
override fun Page(arguments: Bundle?) {
+ val globalRuntimeArgs = remember { getRuntimeArguments(arguments) }
RegularScaffold(title = ArgumentPageModel.create(arguments).genPageTitle()) {
for (entry in buildEntry(arguments)) {
- if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) {
- entry.UiLayout(ArgumentPageModel.buildNextArgument(arguments))
+ if (entry.toPage != null) {
+ entry.UiLayout(
+ mergeArguments(
+ listOf(
+ globalRuntimeArgs,
+ ArgumentPageModel.buildNextArgument(arguments)
+ )
+ )
+ )
} else {
- entry.UiLayout()
+ entry.UiLayout(globalRuntimeArgs)
}
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
index e75d09b..ee2bde4 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
@@ -22,7 +22,6 @@
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavType
import androidx.navigation.navArgument
-import com.android.settingslib.spa.framework.BrowseActivity
import com.android.settingslib.spa.framework.common.EntrySearchData
import com.android.settingslib.spa.framework.common.PageModel
import com.android.settingslib.spa.framework.compose.navigator
@@ -92,14 +91,12 @@
private var arguments: Bundle? = null
private var stringParam: String? = null
private var intParam: Int? = null
- private var highlightName: String? = null
override fun initialize(arguments: Bundle?) {
logMsg("init with args " + arguments.toString())
this.arguments = arguments
stringParam = parameter.getStringArg(STRING_PARAM_NAME, arguments)
intParam = parameter.getIntArg(INT_PARAM_NAME, arguments)
- highlightName = arguments?.getString(BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME)
}
@Composable
@@ -133,7 +130,8 @@
override val title = genPageTitle()
override val summary = stateOf(summaryArray.joinToString(", "))
override val onClick = navigator(
- SettingsPageProviderEnum.ARGUMENT.displayName + parameter.navLink(arguments))
+ SettingsPageProviderEnum.ARGUMENT.displayName + parameter.navLink(arguments)
+ )
}
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
index 9fc736c..89dbcb1 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
@@ -30,6 +30,7 @@
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.getRuntimeArguments
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
import com.android.settingslib.spa.gallery.createSettingsPage
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
@@ -177,9 +178,10 @@
@Composable
override fun Page(arguments: Bundle?) {
+ val globalRuntimeArgs = remember { getRuntimeArguments(arguments) }
RegularScaffold(title = PAGE_TITLE) {
for (entry in buildEntry(arguments)) {
- entry.UiLayout()
+ entry.UiLayout(globalRuntimeArgs)
}
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
index 9a525bf..a4713b9 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
@@ -35,7 +35,7 @@
private const val TITLE = "Sample Category"
object CategoryPageProvider : SettingsPageProvider {
- override val name = "Spinner"
+ override val name = "Category"
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
index c4e88e1..c698d9c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
@@ -153,7 +153,7 @@
"${pageWithEntry.page.displayName} (${pageWithEntry.entries.size})"
override val summary = pageWithEntry.page.formatArguments().toState()
override val onClick =
- navigator(route = ROUTE_PAGE + "/${pageWithEntry.page.id()}")
+ navigator(route = ROUTE_PAGE + "/${pageWithEntry.page.id}")
})
}
}
@@ -172,7 +172,7 @@
val id = arguments!!.getString(PARAM_NAME_PAGE_ID, "")
val pageWithEntry = entryRepository.getPageWithEntry(id)!!
RegularScaffold(title = "Page - ${pageWithEntry.page.displayName}") {
- Text(text = "id = ${pageWithEntry.page.id()}")
+ Text(text = "id = ${pageWithEntry.page.id}")
Text(text = pageWithEntry.page.formatArguments())
Text(text = "Entry size: ${pageWithEntry.entries.size}")
Preference(model = object : PreferenceModel {
@@ -206,7 +206,7 @@
override val title = entry.displayTitle()
override val summary =
"${entry.fromPage?.displayName} -> ${entry.toPage?.displayName}".toState()
- override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id()}")
+ override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id}")
})
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
index 33f1eb5..d923c1c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
@@ -38,8 +38,9 @@
* For gallery, AuthorityPath = com.android.spa.gallery.provider
* For SettingsGoogle, AuthorityPath = com.android.settings.spa.provider
* Some examples:
- * $ adb shell content query --uri content://<AuthorityPath>/page_start
+ * $ adb shell content query --uri content://<AuthorityPath>/page_debug
* $ adb shell content query --uri content://<AuthorityPath>/page_info
+ * $ adb shell content query --uri content://<AuthorityPath>/entry_info
*/
open class EntryProvider(
private val entryRepository: SettingsEntryRepository,
@@ -50,6 +51,7 @@
* Enum to define all column names in provider.
*/
enum class ColumnEnum(val id: String) {
+ // Columns related to page
PAGE_ID("pageId"),
PAGE_NAME("pageName"),
PAGE_ROUTE("pageRoute"),
@@ -57,6 +59,13 @@
PAGE_ENTRY_COUNT("entryCount"),
HAS_RUNTIME_PARAM("hasRuntimeParam"),
PAGE_START_ADB("pageStartAdb"),
+
+ // Columns related to entry
+ ENTRY_ID("entryId"),
+ ENTRY_NAME("entryName"),
+ ENTRY_ROUTE("entryRoute"),
+ ENTRY_TITLE("entryTitle"),
+ ENTRY_SEARCH_KEYWORD("entrySearchKw"),
}
/**
@@ -67,12 +76,15 @@
val queryMatchCode: Int,
val columnNames: List<ColumnEnum>
) {
- PAGE_START_COMMAND(
- "page_start", 1,
+ // For debug
+ PAGE_DEBUG_QUERY(
+ "page_debug", 1,
listOf(ColumnEnum.PAGE_START_ADB)
),
+
+ // page related queries.
PAGE_INFO_QUERY(
- "page_info", 2,
+ "page_info", 100,
listOf(
ColumnEnum.PAGE_ID,
ColumnEnum.PAGE_NAME,
@@ -82,9 +94,24 @@
ColumnEnum.HAS_RUNTIME_PARAM,
)
),
+
+ // entry related queries
+ ENTRY_INFO_QUERY(
+ "entry_info", 200,
+ listOf(
+ ColumnEnum.ENTRY_ID,
+ ColumnEnum.ENTRY_NAME,
+ ColumnEnum.ENTRY_ROUTE,
+ ColumnEnum.ENTRY_TITLE,
+ ColumnEnum.ENTRY_SEARCH_KEYWORD,
+ )
+ )
}
- private var uriMatcher: UriMatcher? = null
+ private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
+ private fun addUri(authority: String, query: QueryEnum) {
+ uriMatcher.addURI(authority, query.queryPath, query.queryMatchCode)
+ }
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
TODO("Implement this to handle requests to delete one or more rows")
@@ -115,18 +142,10 @@
}
override fun attachInfo(context: Context?, info: ProviderInfo?) {
- uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
if (info != null) {
- uriMatcher!!.addURI(
- info.authority,
- QueryEnum.PAGE_START_COMMAND.queryPath,
- QueryEnum.PAGE_START_COMMAND.queryMatchCode
- )
- uriMatcher!!.addURI(
- info.authority,
- QueryEnum.PAGE_INFO_QUERY.queryPath,
- QueryEnum.PAGE_INFO_QUERY.queryMatchCode
- )
+ addUri(info.authority, QueryEnum.PAGE_DEBUG_QUERY)
+ addUri(info.authority, QueryEnum.PAGE_INFO_QUERY)
+ addUri(info.authority, QueryEnum.ENTRY_INFO_QUERY)
}
super.attachInfo(context, info)
}
@@ -139,9 +158,10 @@
sortOrder: String?
): Cursor? {
return try {
- when (uriMatcher!!.match(uri)) {
- QueryEnum.PAGE_START_COMMAND.queryMatchCode -> queryPageStartCommand()
+ when (uriMatcher.match(uri)) {
+ QueryEnum.PAGE_DEBUG_QUERY.queryMatchCode -> queryPageDebug()
QueryEnum.PAGE_INFO_QUERY.queryMatchCode -> queryPageInfo()
+ QueryEnum.ENTRY_INFO_QUERY.queryMatchCode -> queryEntryInfo()
else -> throw UnsupportedOperationException("Unknown Uri $uri")
}
} catch (e: UnsupportedOperationException) {
@@ -152,8 +172,8 @@
}
}
- private fun queryPageStartCommand(): Cursor {
- val cursor = MatrixCursor(QueryEnum.PAGE_START_COMMAND.getColumns())
+ private fun queryPageDebug(): Cursor {
+ val cursor = MatrixCursor(QueryEnum.PAGE_DEBUG_QUERY.getColumns())
for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
val command = createBrowsePageAdbCommand(pageWithEntry.page)
if (command != null) {
@@ -168,7 +188,7 @@
for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
val page = pageWithEntry.page
cursor.newRow()
- .add(ColumnEnum.PAGE_ID.id, page.id())
+ .add(ColumnEnum.PAGE_ID.id, page.id)
.add(ColumnEnum.PAGE_NAME.id, page.displayName)
.add(ColumnEnum.PAGE_ROUTE.id, page.buildRoute())
.add(ColumnEnum.PAGE_ENTRY_COUNT.id, pageWithEntry.entries.size)
@@ -181,6 +201,24 @@
return cursor
}
+ private fun queryEntryInfo(): Cursor {
+ val cursor = MatrixCursor(QueryEnum.ENTRY_INFO_QUERY.getColumns())
+ for (entry in entryRepository.getAllEntries()) {
+ // We can add runtime arguments if necessary
+ val searchData = entry.getSearchData()
+ cursor.newRow()
+ .add(ColumnEnum.ENTRY_ID.id, entry.id)
+ .add(ColumnEnum.ENTRY_NAME.id, entry.displayName)
+ .add(ColumnEnum.ENTRY_ROUTE.id, entry.buildRoute())
+ .add(ColumnEnum.ENTRY_TITLE.id, searchData?.title ?: "")
+ .add(
+ ColumnEnum.ENTRY_SEARCH_KEYWORD.id,
+ searchData?.keyword ?: emptyList<String>()
+ )
+ }
+ return cursor
+ }
+
private fun createBrowsePageIntent(page: SettingsPage): Intent {
if (context == null || browseActivityClass == null || page.hasRuntimeParam())
return Intent()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 2239066..07a6d6c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -17,8 +17,13 @@
package com.android.settingslib.spa.framework.common
import android.os.Bundle
+import android.widget.Toast
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.platform.LocalContext
+import com.android.settingslib.spa.framework.BrowseActivity.Companion.HIGHLIGHT_ENTRY_PARAM_NAME
const val INJECT_ENTRY_NAME = "INJECT"
const val ROOT_ENTRY_NAME = "ROOT"
@@ -27,6 +32,9 @@
* Defines data of a Settings entry.
*/
data class SettingsEntry(
+ // The unique id of this entry, which is computed by name + owner + fromPage + toPage.
+ val id: String,
+
// The name of the page, which is used to compute the unique id, and need to be stable.
private val name: String,
@@ -67,14 +75,9 @@
*/
private val uiLayoutImpl: (@Composable (arguments: Bundle?) -> Unit) = {},
) {
- // The unique id of this entry, which is computed by name + owner + fromPage + toPage.
- fun id(): String {
- return "$name:${owner.id()}(${fromPage?.id()}-${toPage?.id()})".toHashId()
- }
-
fun formatContent(): String {
val content = listOf(
- "id = ${id()}",
+ "id = $id",
"owner = ${owner.formatDisplayTitle()}",
"linkFrom = ${fromPage?.formatDisplayTitle()}",
"linkTo = ${toPage?.formatDisplayTitle()}",
@@ -94,7 +97,7 @@
}
fun buildRoute(): String {
- return containerPage().buildRoute(id())
+ return containerPage().buildRoute(id)
}
fun hasRuntimeParam(): Boolean {
@@ -104,6 +107,7 @@
private fun fullArgument(runtimeArguments: Bundle? = null): Bundle {
val arguments = Bundle()
if (owner.arguments != null) arguments.putAll(owner.arguments)
+ // Put runtime args later, which can override page args.
if (runtimeArguments != null) arguments.putAll(runtimeArguments)
return arguments
}
@@ -114,6 +118,15 @@
@Composable
fun UiLayout(runtimeArguments: Bundle? = null) {
+ val context = LocalContext.current
+ val highlight = rememberSaveable {
+ mutableStateOf(runtimeArguments?.getString(HIGHLIGHT_ENTRY_PARAM_NAME) == id)
+ }
+ if (highlight.value) {
+ highlight.value = false
+ // TODO: Add highlight entry logic
+ Toast.makeText(context, "entry $id highlighted", Toast.LENGTH_SHORT).show()
+ }
uiLayoutImpl(fullArgument(runtimeArguments))
}
}
@@ -135,6 +148,7 @@
fun build(): SettingsEntry {
return SettingsEntry(
+ id = id(),
name = name,
owner = owner,
displayName = displayName,
@@ -190,6 +204,11 @@
return this
}
+ // The unique id of this entry, which is computed by name + owner + fromPage + toPage.
+ private fun id(): String {
+ return "$name:${owner.id}(${fromPage?.id}-${toPage?.id})".toHashId()
+ }
+
companion object {
fun create(entryName: String, owner: SettingsPage): SettingsEntryBuilder {
return SettingsEntryBuilder(entryName, owner)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
index 77f064d..b6f6203 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
@@ -44,29 +44,23 @@
val entryQueue = LinkedList<SettingsEntry>()
for (page in sppRepository.getAllRootPages()) {
val rootEntry = SettingsEntryBuilder.createRoot(owner = page).build()
- val rootEntryId = rootEntry.id()
- if (!entryMap.containsKey(rootEntryId)) {
+ if (!entryMap.containsKey(rootEntry.id)) {
entryQueue.push(rootEntry)
- entryMap.put(rootEntryId, rootEntry)
+ entryMap.put(rootEntry.id, rootEntry)
}
}
while (entryQueue.isNotEmpty() && entryMap.size < MAX_ENTRY_SIZE) {
val entry = entryQueue.pop()
val page = entry.toPage
- val pageId = page?.id()
- if (pageId == null || pageWithEntryMap.containsKey(pageId)) continue
+ if (page == null || pageWithEntryMap.containsKey(page.id)) continue
val spp = sppRepository.getProviderOrNull(page.name) ?: continue
- val newEntries = spp.buildEntry(page.arguments).map {
- // Set from-page if it is missing.
- if (it.fromPage == null) it.copy(fromPage = page) else it
- }
- pageWithEntryMap[pageId] = SettingsPageWithEntry(page, newEntries)
+ val newEntries = spp.buildEntry(page.arguments)
+ pageWithEntryMap[page.id] = SettingsPageWithEntry(page, newEntries)
for (newEntry in newEntries) {
- val newEntryId = newEntry.id()
- if (!entryMap.containsKey(newEntryId)) {
+ if (!entryMap.containsKey(newEntry.id)) {
entryQueue.push(newEntry)
- entryMap.put(newEntryId, newEntry)
+ entryMap.put(newEntry.id, newEntry)
}
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 124743a..0c301b9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -26,6 +26,9 @@
* Defines data to identify a Settings page.
*/
data class SettingsPage(
+ // The unique id of this page, which is computed by name + normalized(arguments)
+ val id: String,
+
// The name of the page, which is used to compute the unique id, and need to be stable.
val name: String,
@@ -41,17 +44,28 @@
companion object {
fun create(
name: String,
+ displayName: String? = null,
parameter: List<NamedNavArgument> = emptyList(),
arguments: Bundle? = null
): SettingsPage {
- return SettingsPage(name, name, parameter, arguments)
+ return SettingsPage(
+ id = id(name, parameter, arguments),
+ name = name,
+ displayName = displayName ?: name,
+ parameter = parameter,
+ arguments = arguments
+ )
}
- }
- // The unique id of this page, which is computed by name + normalized(arguments)
- fun id(): String {
- val normArguments = parameter.normalize(arguments)
- return "$name:${normArguments?.toString()}".toHashId()
+ // The unique id of this page, which is computed by name + normalized(arguments)
+ private fun id(
+ name: String,
+ parameter: List<NamedNavArgument> = emptyList(),
+ arguments: Bundle? = null
+ ): String {
+ val normArguments = parameter.normalize(arguments)
+ return "$name:${normArguments?.toString()}".toHashId()
+ }
}
// Returns if this Settings Page is created by the given Spp.
@@ -69,12 +83,12 @@
return "$displayName ${formatArguments()}"
}
- fun buildRoute(highlightEntryName: String? = null): String {
+ fun buildRoute(highlightEntryId: String? = null): String {
val highlightParam =
- if (highlightEntryName == null)
+ if (highlightEntryId == null)
""
else
- "?${BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME}=$highlightEntryName"
+ "?${BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME}=$highlightEntryId"
return name + parameter.navLink(arguments) + highlightParam
}
@@ -114,5 +128,5 @@
}
fun String.toHashId(): String {
- return this.hashCode().toString(36)
+ return this.hashCode().toUInt().toString(36)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
index d7d7750..452f76a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
@@ -19,6 +19,7 @@
import android.os.Bundle
import androidx.navigation.NamedNavArgument
import androidx.navigation.NavType
+import com.android.settingslib.spa.framework.BrowseActivity
fun List<NamedNavArgument>.navRoute(): String {
return this.joinToString("") { argument -> "/{${argument.name}}" }
@@ -70,3 +71,21 @@
}
return false
}
+
+fun getRuntimeArguments(arguments: Bundle? = null): Bundle {
+ val res = Bundle()
+ val highlightEntry = arguments?.getString(BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME)
+ if (highlightEntry != null) {
+ res.putString(BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME, highlightEntry)
+ }
+ // Append more general runtime arguments here
+ return res
+}
+
+fun mergeArguments(argsList: List<Bundle?>): Bundle {
+ val res = Bundle()
+ for (args in argsList) {
+ if (args != null) res.putAll(args)
+ }
+ return res
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 80b2364..883eddf 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -63,7 +63,7 @@
override val parameter = PAGE_PARAMETER
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- val owner = SettingsPage.create(name, parameter, arguments)
+ val owner = SettingsPage.create(name, parameter = parameter, arguments = arguments)
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
SettingsEntryBuilder.create(ENTRY_NAME, owner).setIsAllowSearch(false).build()
@@ -108,7 +108,10 @@
fun buildPageData(permissionType: String): SettingsPage {
return SettingsPage.create(
- PAGE_NAME, PAGE_PARAMETER, bundleOf(PERMISSION to permissionType))
+ name = PAGE_NAME,
+ parameter = PAGE_PARAMETER,
+ arguments = bundleOf(PERMISSION to permissionType)
+ )
}
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index bd60bd36..ec7d75e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -63,7 +63,7 @@
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val permissionType = parameter.getStringArg(PERMISSION, arguments)!!
- val appListPage = SettingsPage.create(name, parameter, arguments)
+ val appListPage = SettingsPage.create(name, parameter = parameter, arguments = arguments)
val appInfoPage = TogglePermissionAppInfoPageProvider.buildPageData(permissionType)
val entryList = mutableListOf<SettingsEntry>()
// TODO: add more categories, such as personal, work, cloned, etc.
@@ -117,7 +117,10 @@
listModelSupplier: (Context) -> TogglePermissionAppListModel<out AppRecord>,
): SettingsEntryBuilder {
val appListPage = SettingsPage.create(
- PAGE_NAME, PAGE_PARAMETER, bundleOf(PERMISSION to permissionType))
+ name = PAGE_NAME,
+ parameter = PAGE_PARAMETER,
+ arguments = bundleOf(PERMISSION to permissionType)
+ )
return SettingsEntryBuilder.createInject(owner = appListPage).setIsAllowSearch(false)
.setUiLayoutFn {
val listModel = rememberContext(listModelSupplier)
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 123c01b..79fb566 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -210,13 +210,15 @@
LocalBluetoothLeBroadcast(Context context) {
mExecutor = Executors.newSingleThreadExecutor();
- BluetoothAdapter.getDefaultAdapter().
- getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST);
mBuilder = new BluetoothLeAudioContentMetadata.Builder();
mContentResolver = context.getContentResolver();
Handler handler = new Handler(Looper.getMainLooper());
mSettingsObserver = new BroadcastSettingsObserver(handler);
updateBroadcastInfoFromContentProvider();
+
+ // Before registering callback, the constructor should finish creating the all of variables.
+ BluetoothAdapter.getDefaultAdapter()
+ .getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST);
}
/**
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index f05f637..b1979c9 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -768,6 +768,7 @@
Settings.Secure.SMS_DEFAULT_APPLICATION,
Settings.Secure.SPELL_CHECKER_ENABLED, // Intentionally removed in Q
Settings.Secure.TRUST_AGENTS_INITIALIZED,
+ Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED,
Settings.Secure.TV_APP_USES_NON_SYSTEM_INPUTS,
Settings.Secure.TV_INPUT_CUSTOM_LABELS,
Settings.Secure.TV_INPUT_HIDDEN_INPUTS,
diff --git a/packages/SystemUI/res/drawable/qs_hotspot_icon_off.xml b/packages/SystemUI/res/drawable/qs_hotspot_icon_off.xml
new file mode 100644
index 0000000..eb160de
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_hotspot_icon_off.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_0_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="133"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="0.3"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_2_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="133"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="0.3"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="150"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M0 -1 C-1.1,-1 -2,-0.1 -2,1 C-2,1.55 -1.77,2.05 -1.41,2.41 C-1.05,2.77 -0.55,3 0,3 C0.55,3 1.05,2.77 1.41,2.41 C1.77,2.05 2,1.55 2,1 C2,-0.1 1.1,-1 0,-1c " />
+ <path
+ android:name="_R_G_L_0_G_D_1_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M0 -5 C-3.31,-5 -6,-2.31 -6,1 C-6,2.66 -5.32,4.15 -4.24,5.24 C-4.24,5.24 -2.82,3.82 -2.82,3.82 C-3.55,3.1 -4,2.11 -4,1 C-4,-1.21 -2.21,-3 0,-3 C2.21,-3 4,-1.21 4,1 C4,2.11 3.55,3.1 2.82,3.82 C2.82,3.82 4.24,5.24 4.24,5.24 C5.32,4.15 6,2.66 6,1 C6,-2.31 3.31,-5 0,-5c " />
+ <path
+ android:name="_R_G_L_0_G_D_2_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M0 -9 C-5.52,-9 -10,-4.52 -10,1 C-10,3.76 -8.88,6.26 -7.07,8.07 C-7.07,8.07 -5.66,6.66 -5.66,6.66 C-7.1,5.21 -8,3.21 -8,1 C-8,-3.42 -4.42,-7 0,-7 C4.42,-7 8,-3.42 8,1 C8,3.21 7.1,5.2 5.65,6.65 C5.65,6.65 7.06,8.06 7.06,8.06 C8.88,6.26 10,3.76 10,1 C10,-4.52 5.52,-9 0,-9c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_hotspot_icon_on.xml b/packages/SystemUI/res/drawable/qs_hotspot_icon_on.xml
new file mode 100644
index 0000000..de972a6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_hotspot_icon_on.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_0_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0.3"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_2_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0.3"
+ android:valueTo="0.3"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="fillAlpha"
+ android:startOffset="167"
+ android:valueFrom="0.3"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="433"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M0 -1 C-1.1,-1 -2,-0.1 -2,1 C-2,1.55 -1.77,2.05 -1.41,2.41 C-1.05,2.77 -0.55,3 0,3 C0.55,3 1.05,2.77 1.41,2.41 C1.77,2.05 2,1.55 2,1 C2,-0.1 1.1,-1 0,-1c " />
+ <path
+ android:name="_R_G_L_0_G_D_1_P_0"
+ android:fillAlpha="0.3"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M0 -5 C-3.31,-5 -6,-2.31 -6,1 C-6,2.66 -5.32,4.15 -4.24,5.24 C-4.24,5.24 -2.82,3.82 -2.82,3.82 C-3.55,3.1 -4,2.11 -4,1 C-4,-1.21 -2.21,-3 0,-3 C2.21,-3 4,-1.21 4,1 C4,2.11 3.55,3.1 2.82,3.82 C2.82,3.82 4.24,5.24 4.24,5.24 C5.32,4.15 6,2.66 6,1 C6,-2.31 3.31,-5 0,-5c " />
+ <path
+ android:name="_R_G_L_0_G_D_2_P_0"
+ android:fillAlpha="0.3"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M0 -9 C-5.52,-9 -10,-4.52 -10,1 C-10,3.76 -8.88,6.26 -7.07,8.07 C-7.07,8.07 -5.66,6.66 -5.66,6.66 C-7.1,5.21 -8,3.21 -8,1 C-8,-3.42 -4.42,-7 0,-7 C4.42,-7 8,-3.42 8,1 C8,3.21 7.1,5.2 5.65,6.65 C5.65,6.65 7.06,8.06 7.06,8.06 C8.88,6.26 10,3.76 10,1 C10,-4.52 5.52,-9 0,-9c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_hotspot_icon_search.xml b/packages/SystemUI/res/drawable/qs_hotspot_icon_search.xml
new file mode 100644
index 0000000..e33b264
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_hotspot_icon_search.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_0_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0.3"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="fillAlpha"
+ android:startOffset="250"
+ android:valueFrom="1"
+ android:valueTo="0.3"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_2_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0.3"
+ android:valueTo="0.3"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="fillAlpha"
+ android:startOffset="167"
+ android:valueFrom="0.3"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="fillAlpha"
+ android:startOffset="417"
+ android:valueFrom="1"
+ android:valueTo="0.3"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="850"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M0 -1 C-1.1,-1 -2,-0.1 -2,1 C-2,1.55 -1.77,2.05 -1.41,2.41 C-1.05,2.77 -0.55,3 0,3 C0.55,3 1.05,2.77 1.41,2.41 C1.77,2.05 2,1.55 2,1 C2,-0.1 1.1,-1 0,-1c " />
+ <path
+ android:name="_R_G_L_0_G_D_1_P_0"
+ android:fillAlpha="0.3"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M0 -5 C-3.31,-5 -6,-2.31 -6,1 C-6,2.66 -5.32,4.15 -4.24,5.24 C-4.24,5.24 -2.82,3.82 -2.82,3.82 C-3.55,3.1 -4,2.11 -4,1 C-4,-1.21 -2.21,-3 0,-3 C2.21,-3 4,-1.21 4,1 C4,2.11 3.55,3.1 2.82,3.82 C2.82,3.82 4.24,5.24 4.24,5.24 C5.32,4.15 6,2.66 6,1 C6,-2.31 3.31,-5 0,-5c " />
+ <path
+ android:name="_R_G_L_0_G_D_2_P_0"
+ android:fillAlpha="0.3"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M0 -9 C-5.52,-9 -10,-4.52 -10,1 C-10,3.76 -8.88,6.26 -7.07,8.07 C-7.07,8.07 -5.66,6.66 -5.66,6.66 C-7.1,5.21 -8,3.21 -8,1 C-8,-3.42 -4.42,-7 0,-7 C4.42,-7 8,-3.42 8,1 C8,3.21 7.1,5.2 5.65,6.65 C5.65,6.65 7.06,8.06 7.06,8.06 C8.88,6.26 10,3.76 10,1 C10,-4.52 5.52,-9 0,-9c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
index acb47f7..2d67d95 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -14,8 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.systemui.dreams.complication.DoubleShadowTextClock
+<com.android.systemui.shared.shadow.DoubleShadowTextClock
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/time_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -25,4 +26,13 @@
android:format24Hour="@string/dream_time_complication_24_hr_time_format"
android:fontFeatureSettings="pnum, lnum"
android:letterSpacing="0.02"
- android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"/>
+ android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"
+ app:keyShadowBlur="@dimen/dream_overlay_clock_key_text_shadow_radius"
+ app:keyShadowOffsetX="@dimen/dream_overlay_clock_key_text_shadow_dx"
+ app:keyShadowOffsetY="@dimen/dream_overlay_clock_key_text_shadow_dy"
+ app:keyShadowAlpha="0.3"
+ app:ambientShadowBlur="@dimen/dream_overlay_clock_ambient_text_shadow_radius"
+ app:ambientShadowOffsetX="@dimen/dream_overlay_clock_ambient_text_shadow_dx"
+ app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy"
+ app:ambientShadowAlpha="0.3"
+/>
diff --git a/packages/SystemUI/shared/res/values/attrs.xml b/packages/SystemUI/shared/res/values/attrs.xml
index f9d66ee..96a5840 100644
--- a/packages/SystemUI/shared/res/values/attrs.xml
+++ b/packages/SystemUI/shared/res/values/attrs.xml
@@ -25,4 +25,39 @@
<attr name="lockScreenWeight" format="integer" />
<attr name="chargeAnimationDelay" format="integer" />
</declare-styleable>
+
+ <declare-styleable name="DoubleShadowAttrDeclare">
+ <attr name="keyShadowBlur" format="dimension" />
+ <attr name="keyShadowOffsetX" format="dimension" />
+ <attr name="keyShadowOffsetY" format="dimension" />
+ <attr name="keyShadowAlpha" format="float" />
+ <attr name="ambientShadowBlur" format="dimension" />
+ <attr name="ambientShadowOffsetX" format="dimension" />
+ <attr name="ambientShadowOffsetY" format="dimension" />
+ <attr name="ambientShadowAlpha" format="float" />
+ </declare-styleable>
+
+ <declare-styleable name="DoubleShadowTextClock">
+ <attr name="keyShadowBlur" />
+ <attr name="keyShadowOffsetX" />
+ <attr name="keyShadowOffsetY" />
+ <attr name="keyShadowAlpha" />
+ <attr name="ambientShadowBlur" />
+ <attr name="ambientShadowOffsetX" />
+ <attr name="ambientShadowOffsetY" />
+ <attr name="ambientShadowAlpha" />
+ </declare-styleable>
+
+ <declare-styleable name="DoubleShadowTextView">
+ <attr name="keyShadowBlur" />
+ <attr name="keyShadowOffsetX" />
+ <attr name="keyShadowOffsetY" />
+ <attr name="keyShadowAlpha" />
+ <attr name="ambientShadowBlur" />
+ <attr name="ambientShadowOffsetX" />
+ <attr name="ambientShadowOffsetY" />
+ <attr name="ambientShadowAlpha" />
+ <attr name="drawableIconSize" format="dimension" />
+ <attr name="drawableIconInsetSize" format="dimension" />
+ </declare-styleable>
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt
new file mode 100644
index 0000000..3748eba
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.shadow
+
+import android.graphics.BlendMode
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorFilter
+import android.graphics.PixelFormat
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.graphics.RenderEffect
+import android.graphics.RenderNode
+import android.graphics.Shader
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.InsetDrawable
+import com.android.systemui.shared.shadow.DoubleShadowTextHelper.ShadowInfo
+
+/** A component to draw an icon with two layers of shadows. */
+class DoubleShadowIconDrawable(
+ keyShadowInfo: ShadowInfo,
+ ambientShadowInfo: ShadowInfo,
+ iconDrawable: Drawable,
+ iconSize: Int,
+ val iconInsetSize: Int
+) : Drawable() {
+ private val mAmbientShadowInfo: ShadowInfo
+ private val mCanvasSize: Int
+ private val mKeyShadowInfo: ShadowInfo
+ private val mIconDrawable: InsetDrawable
+ private val mDoubleShadowNode: RenderNode
+
+ init {
+ mCanvasSize = iconSize + iconInsetSize * 2
+ mKeyShadowInfo = keyShadowInfo
+ mAmbientShadowInfo = ambientShadowInfo
+ setBounds(0, 0, mCanvasSize, mCanvasSize)
+ mIconDrawable = InsetDrawable(iconDrawable, iconInsetSize)
+ mIconDrawable.setBounds(0, 0, mCanvasSize, mCanvasSize)
+ mDoubleShadowNode = createShadowRenderNode()
+ }
+
+ private fun createShadowRenderNode(): RenderNode {
+ val renderNode = RenderNode("DoubleShadowNode")
+ renderNode.setPosition(0, 0, mCanvasSize, mCanvasSize)
+ // Create render effects
+ val ambientShadow =
+ createShadowRenderEffect(
+ mAmbientShadowInfo.blur,
+ mAmbientShadowInfo.offsetX,
+ mAmbientShadowInfo.offsetY,
+ mAmbientShadowInfo.alpha
+ )
+ val keyShadow =
+ createShadowRenderEffect(
+ mKeyShadowInfo.blur,
+ mKeyShadowInfo.offsetX,
+ mKeyShadowInfo.offsetY,
+ mKeyShadowInfo.alpha
+ )
+ val blend = RenderEffect.createBlendModeEffect(ambientShadow, keyShadow, BlendMode.DARKEN)
+ renderNode.setRenderEffect(blend)
+ return renderNode
+ }
+
+ private fun createShadowRenderEffect(
+ radius: Float,
+ offsetX: Float,
+ offsetY: Float,
+ alpha: Float
+ ): RenderEffect {
+ return RenderEffect.createColorFilterEffect(
+ PorterDuffColorFilter(Color.argb(alpha, 0f, 0f, 0f), PorterDuff.Mode.MULTIPLY),
+ RenderEffect.createOffsetEffect(
+ offsetX,
+ offsetY,
+ RenderEffect.createBlurEffect(radius, radius, Shader.TileMode.CLAMP)
+ )
+ )
+ }
+
+ override fun draw(canvas: Canvas) {
+ if (canvas.isHardwareAccelerated) {
+ if (!mDoubleShadowNode.hasDisplayList()) {
+ // Record render node if its display list is not recorded or discarded
+ // (which happens when it's no longer drawn by anything).
+ val recordingCanvas = mDoubleShadowNode.beginRecording()
+ mIconDrawable.draw(recordingCanvas)
+ mDoubleShadowNode.endRecording()
+ }
+ canvas.drawRenderNode(mDoubleShadowNode)
+ }
+ mIconDrawable.draw(canvas)
+ }
+
+ override fun getOpacity(): Int {
+ return PixelFormat.TRANSPARENT
+ }
+
+ override fun setAlpha(alpha: Int) {
+ mIconDrawable.alpha = alpha
+ }
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {
+ mIconDrawable.colorFilter = colorFilter
+ }
+
+ override fun setTint(color: Int) {
+ mIconDrawable.setTint(color)
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt
new file mode 100644
index 0000000..f2db129
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.shadow
+
+import android.content.Context
+import android.graphics.Canvas
+import android.util.AttributeSet
+import android.widget.TextClock
+import com.android.systemui.shared.R
+import com.android.systemui.shared.shadow.DoubleShadowTextHelper.ShadowInfo
+import com.android.systemui.shared.shadow.DoubleShadowTextHelper.applyShadows
+
+/** Extension of [TextClock] which draws two shadows on the text (ambient and key shadows) */
+class DoubleShadowTextClock
+@JvmOverloads
+constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : TextClock(context, attrs, defStyleAttr, defStyleRes) {
+ private val mAmbientShadowInfo: ShadowInfo
+ private val mKeyShadowInfo: ShadowInfo
+
+ init {
+ val attributes =
+ context.obtainStyledAttributes(
+ attrs,
+ R.styleable.DoubleShadowTextClock,
+ defStyleAttr,
+ defStyleRes
+ )
+ try {
+ val keyShadowBlur =
+ attributes.getDimensionPixelSize(R.styleable.DoubleShadowTextClock_keyShadowBlur, 0)
+ val keyShadowOffsetX =
+ attributes.getDimensionPixelSize(
+ R.styleable.DoubleShadowTextClock_keyShadowOffsetX,
+ 0
+ )
+ val keyShadowOffsetY =
+ attributes.getDimensionPixelSize(
+ R.styleable.DoubleShadowTextClock_keyShadowOffsetY,
+ 0
+ )
+ val keyShadowAlpha =
+ attributes.getFloat(R.styleable.DoubleShadowTextClock_keyShadowAlpha, 0f)
+ mKeyShadowInfo =
+ ShadowInfo(
+ keyShadowBlur.toFloat(),
+ keyShadowOffsetX.toFloat(),
+ keyShadowOffsetY.toFloat(),
+ keyShadowAlpha
+ )
+ val ambientShadowBlur =
+ attributes.getDimensionPixelSize(
+ R.styleable.DoubleShadowTextClock_ambientShadowBlur,
+ 0
+ )
+ val ambientShadowOffsetX =
+ attributes.getDimensionPixelSize(
+ R.styleable.DoubleShadowTextClock_ambientShadowOffsetX,
+ 0
+ )
+ val ambientShadowOffsetY =
+ attributes.getDimensionPixelSize(
+ R.styleable.DoubleShadowTextClock_ambientShadowOffsetY,
+ 0
+ )
+ val ambientShadowAlpha =
+ attributes.getFloat(R.styleable.DoubleShadowTextClock_ambientShadowAlpha, 0f)
+ mAmbientShadowInfo =
+ ShadowInfo(
+ ambientShadowBlur.toFloat(),
+ ambientShadowOffsetX.toFloat(),
+ ambientShadowOffsetY.toFloat(),
+ ambientShadowAlpha
+ )
+ } finally {
+ attributes.recycle()
+ }
+ }
+
+ public override fun onDraw(canvas: Canvas) {
+ applyShadows(mKeyShadowInfo, mAmbientShadowInfo, this, canvas) { super.onDraw(canvas) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextHelper.kt
similarity index 77%
rename from packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt
rename to packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextHelper.kt
index b1dc5a2..eaac93d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextHelper.kt
@@ -14,31 +14,33 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.complication
+package com.android.systemui.shared.shadow
import android.graphics.Canvas
+import android.graphics.Color
import android.widget.TextView
-import androidx.annotation.ColorInt
-class DoubleShadowTextHelper
-constructor(
- private val keyShadowInfo: ShadowInfo,
- private val ambientShadowInfo: ShadowInfo,
-) {
+object DoubleShadowTextHelper {
data class ShadowInfo(
val blur: Float,
val offsetX: Float = 0f,
val offsetY: Float = 0f,
- @ColorInt val color: Int
+ val alpha: Float
)
- fun applyShadows(view: TextView, canvas: Canvas, onDrawCallback: () -> Unit) {
+ fun applyShadows(
+ keyShadowInfo: ShadowInfo,
+ ambientShadowInfo: ShadowInfo,
+ view: TextView,
+ canvas: Canvas,
+ onDrawCallback: () -> Unit
+ ) {
// We enhance the shadow by drawing the shadow twice
view.paint.setShadowLayer(
ambientShadowInfo.blur,
ambientShadowInfo.offsetX,
ambientShadowInfo.offsetY,
- ambientShadowInfo.color
+ Color.argb(ambientShadowInfo.alpha, 0f, 0f, 0f)
)
onDrawCallback()
canvas.save()
@@ -53,7 +55,7 @@
keyShadowInfo.blur,
keyShadowInfo.offsetX,
keyShadowInfo.offsetY,
- keyShadowInfo.color
+ Color.argb(keyShadowInfo.alpha, 0f, 0f, 0f)
)
onDrawCallback()
canvas.restore()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt
new file mode 100644
index 0000000..25d2721
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.shadow
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.widget.TextView
+import com.android.systemui.shared.R
+import com.android.systemui.shared.shadow.DoubleShadowTextHelper.ShadowInfo
+import com.android.systemui.shared.shadow.DoubleShadowTextHelper.applyShadows
+
+/** Extension of [TextView] which draws two shadows on the text (ambient and key shadows} */
+class DoubleShadowTextView
+@JvmOverloads
+constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : TextView(context, attrs, defStyleAttr, defStyleRes) {
+ private val mKeyShadowInfo: ShadowInfo
+ private val mAmbientShadowInfo: ShadowInfo
+
+ init {
+ val attributes =
+ context.obtainStyledAttributes(
+ attrs,
+ R.styleable.DoubleShadowTextView,
+ defStyleAttr,
+ defStyleRes
+ )
+ val drawableSize: Int
+ val drawableInsetSize: Int
+ try {
+ val keyShadowBlur =
+ attributes.getDimensionPixelSize(R.styleable.DoubleShadowTextView_keyShadowBlur, 0)
+ val keyShadowOffsetX =
+ attributes.getDimensionPixelSize(
+ R.styleable.DoubleShadowTextView_keyShadowOffsetX,
+ 0
+ )
+ val keyShadowOffsetY =
+ attributes.getDimensionPixelSize(
+ R.styleable.DoubleShadowTextView_keyShadowOffsetY,
+ 0
+ )
+ val keyShadowAlpha =
+ attributes.getFloat(R.styleable.DoubleShadowTextView_keyShadowAlpha, 0f)
+ mKeyShadowInfo =
+ ShadowInfo(
+ keyShadowBlur.toFloat(),
+ keyShadowOffsetX.toFloat(),
+ keyShadowOffsetY.toFloat(),
+ keyShadowAlpha
+ )
+ val ambientShadowBlur =
+ attributes.getDimensionPixelSize(
+ R.styleable.DoubleShadowTextView_ambientShadowBlur,
+ 0
+ )
+ val ambientShadowOffsetX =
+ attributes.getDimensionPixelSize(
+ R.styleable.DoubleShadowTextView_ambientShadowOffsetX,
+ 0
+ )
+ val ambientShadowOffsetY =
+ attributes.getDimensionPixelSize(
+ R.styleable.DoubleShadowTextView_ambientShadowOffsetY,
+ 0
+ )
+ val ambientShadowAlpha =
+ attributes.getFloat(R.styleable.DoubleShadowTextView_ambientShadowAlpha, 0f)
+ mAmbientShadowInfo =
+ ShadowInfo(
+ ambientShadowBlur.toFloat(),
+ ambientShadowOffsetX.toFloat(),
+ ambientShadowOffsetY.toFloat(),
+ ambientShadowAlpha
+ )
+ drawableSize =
+ attributes.getDimensionPixelSize(
+ R.styleable.DoubleShadowTextView_drawableIconSize,
+ 0
+ )
+ drawableInsetSize =
+ attributes.getDimensionPixelSize(
+ R.styleable.DoubleShadowTextView_drawableIconInsetSize,
+ 0
+ )
+ } finally {
+ attributes.recycle()
+ }
+
+ val drawables = arrayOf<Drawable?>(null, null, null, null)
+ for ((index, drawable) in compoundDrawablesRelative.withIndex()) {
+ if (drawable == null) continue
+ drawables[index] =
+ DoubleShadowIconDrawable(
+ mKeyShadowInfo,
+ mAmbientShadowInfo,
+ drawable,
+ drawableSize,
+ drawableInsetSize
+ )
+ }
+ setCompoundDrawablesRelative(drawables[0], drawables[1], drawables[2], drawables[3])
+ }
+
+ public override fun onDraw(canvas: Canvas) {
+ applyShadows(mKeyShadowInfo, mAmbientShadowInfo, this, canvas) { super.onDraw(canvas) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 2cc5ccdc..1e5c53d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -24,6 +24,7 @@
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
import android.animation.Animator;
@@ -106,6 +107,8 @@
return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_password;
+ case PROMPT_REASON_TRUSTAGENT_EXPIRED:
+ return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_NONE:
return 0;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 9871645..5b22324 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -330,6 +330,9 @@
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
break;
+ case PROMPT_REASON_TRUSTAGENT_EXPIRED:
+ mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+ break;
case PROMPT_REASON_NONE:
break;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index c46e33d..0a91150 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -22,6 +22,7 @@
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
import android.animation.Animator;
@@ -123,6 +124,8 @@
return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_pin;
+ case PROMPT_REASON_TRUSTAGENT_EXPIRED:
+ return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_NONE:
return 0;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index ac00e94..9d0a8ac 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -61,6 +61,12 @@
int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7;
/**
+ * Some auth is required because the trustagent expired either from timeout or manually by
+ * the user
+ */
+ int PROMPT_REASON_TRUSTAGENT_EXPIRED = 8;
+
+ /**
* Reset the view and prepare to take input. This should do things like clearing the
* password or pattern and clear error messages.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 7fc8123..a5fdc68 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -103,7 +103,6 @@
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -253,7 +252,6 @@
@Inject Lazy<UserInfoController> mUserInfoController;
@Inject Lazy<KeyguardStateController> mKeyguardMonitor;
@Inject Lazy<KeyguardUpdateMonitor> mKeyguardUpdateMonitor;
- @Inject Lazy<BatteryController> mBatteryController;
@Inject Lazy<NightDisplayListener> mNightDisplayListener;
@Inject Lazy<ReduceBrightColorsController> mReduceBrightColorsController;
@Inject Lazy<ManagedProfileController> mManagedProfileController;
@@ -404,8 +402,6 @@
mProviders.put(UserInfoController.class, mUserInfoController::get);
- mProviders.put(BatteryController.class, mBatteryController::get);
-
mProviders.put(NightDisplayListener.class, mNightDisplayListener::get);
mProviders.put(ReduceBrightColorsController.class, mReduceBrightColorsController::get);
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 2e13903..67b683e 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -455,7 +455,6 @@
}
}
- boolean needToUpdateProviderViews = false;
final String newUniqueId = mDisplayInfo.uniqueId;
if (!Objects.equals(newUniqueId, mDisplayUniqueId)) {
mDisplayUniqueId = newUniqueId;
@@ -473,37 +472,6 @@
setupDecorations();
return;
}
-
- if (mScreenDecorHwcLayer != null) {
- updateHwLayerRoundedCornerDrawable();
- updateHwLayerRoundedCornerExistAndSize();
- }
- needToUpdateProviderViews = true;
- }
-
- final float newRatio = getPhysicalPixelDisplaySizeRatio();
- if (mRoundedCornerResDelegate.getPhysicalPixelDisplaySizeRatio() != newRatio) {
- mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(newRatio);
- if (mScreenDecorHwcLayer != null) {
- updateHwLayerRoundedCornerExistAndSize();
- }
- needToUpdateProviderViews = true;
- }
-
- if (needToUpdateProviderViews) {
- updateOverlayProviderViews(null);
- } else {
- updateOverlayProviderViews(new Integer[] {
- mFaceScanningViewId,
- R.id.display_cutout,
- R.id.display_cutout_left,
- R.id.display_cutout_right,
- R.id.display_cutout_bottom,
- });
- }
-
- if (mScreenDecorHwcLayer != null) {
- mScreenDecorHwcLayer.onDisplayChanged(newUniqueId);
}
}
};
@@ -1069,6 +1037,8 @@
&& (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod))) {
mRotation = newRotation;
mDisplayMode = newMod;
+ mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
+ getPhysicalPixelDisplaySizeRatio());
if (mScreenDecorHwcLayer != null) {
mScreenDecorHwcLayer.pendingConfigChange = false;
mScreenDecorHwcLayer.updateRotation(mRotation);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 56103773..1ceb6b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -187,7 +187,10 @@
public void onReceive(Context context, Intent intent) {
if (mCurrentDialog != null
&& Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
- Log.w(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received");
+ String reason = intent.getStringExtra("reason");
+ reason = (reason != null) ? reason : "unknown";
+ Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, reason: " + reason);
+
mCurrentDialog.dismissWithoutCallback(true /* animate */);
mCurrentDialog = null;
@@ -370,18 +373,14 @@
@Override
public void onTryAgainPressed(long requestId) {
- if (mReceiver == null) {
- Log.e(TAG, "onTryAgainPressed: Receiver is null");
- return;
- }
-
- if (requestId != mCurrentDialog.getRequestId()) {
- Log.w(TAG, "requestId doesn't match, skip onTryAgainPressed");
+ final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId);
+ if (receiver == null) {
+ Log.w(TAG, "Skip onTryAgainPressed");
return;
}
try {
- mReceiver.onTryAgainPressed();
+ receiver.onTryAgainPressed();
} catch (RemoteException e) {
Log.e(TAG, "RemoteException when handling try again", e);
}
@@ -389,18 +388,14 @@
@Override
public void onDeviceCredentialPressed(long requestId) {
- if (mReceiver == null) {
- Log.e(TAG, "onDeviceCredentialPressed: Receiver is null");
- return;
- }
-
- if (requestId != mCurrentDialog.getRequestId()) {
- Log.w(TAG, "requestId doesn't match, skip onDeviceCredentialPressed");
+ final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId);
+ if (receiver == null) {
+ Log.w(TAG, "Skip onDeviceCredentialPressed");
return;
}
try {
- mReceiver.onDeviceCredentialPressed();
+ receiver.onDeviceCredentialPressed();
} catch (RemoteException e) {
Log.e(TAG, "RemoteException when handling credential button", e);
}
@@ -408,18 +403,14 @@
@Override
public void onSystemEvent(int event, long requestId) {
- if (mReceiver == null) {
- Log.e(TAG, "onSystemEvent(" + event + "): Receiver is null");
- return;
- }
-
- if (requestId != mCurrentDialog.getRequestId()) {
- Log.w(TAG, "requestId doesn't match, skip onSystemEvent");
+ final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId);
+ if (receiver == null) {
+ Log.w(TAG, "Skip onSystemEvent");
return;
}
try {
- mReceiver.onSystemEvent(event);
+ receiver.onSystemEvent(event);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException when sending system event", e);
}
@@ -427,23 +418,46 @@
@Override
public void onDialogAnimatedIn(long requestId) {
- if (mReceiver == null) {
- Log.e(TAG, "onDialogAnimatedIn: Receiver is null");
- return;
- }
-
- if (requestId != mCurrentDialog.getRequestId()) {
- Log.w(TAG, "requestId doesn't match, skip onDialogAnimatedIn");
+ final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId);
+ if (receiver == null) {
+ Log.w(TAG, "Skip onDialogAnimatedIn");
return;
}
try {
- mReceiver.onDialogAnimatedIn();
+ receiver.onDialogAnimatedIn();
} catch (RemoteException e) {
Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e);
}
}
+ @Nullable
+ private IBiometricSysuiReceiver getCurrentReceiver(long requestId) {
+ if (!isRequestIdValid(requestId)) {
+ return null;
+ }
+
+ if (mReceiver == null) {
+ Log.w(TAG, "getCurrentReceiver: Receiver is null");
+ }
+
+ return mReceiver;
+ }
+
+ private boolean isRequestIdValid(long requestId) {
+ if (mCurrentDialog == null) {
+ Log.w(TAG, "shouldNotifyReceiver: dialog already gone");
+ return false;
+ }
+
+ if (requestId != mCurrentDialog.getRequestId()) {
+ Log.w(TAG, "shouldNotifyReceiver: requestId doesn't match");
+ return false;
+ }
+
+ return true;
+ }
+
@Override
public void onDismissed(@DismissedReason int reason,
@Nullable byte[] credentialAttestation, long requestId) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 36287f5..27e9af9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -342,8 +342,11 @@
if (mOverlay != null
&& mOverlay.getRequestReason() != REASON_AUTH_KEYGUARD
&& Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
- Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, mRequestReason: "
- + mOverlay.getRequestReason());
+ String reason = intent.getStringExtra("reason");
+ reason = (reason != null) ? reason : "unknown";
+ Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, reason: " + reason
+ + ", mRequestReason: " + mOverlay.getRequestReason());
+
mOverlay.cancel();
hideUdfpsOverlay();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index fbfc94a..a996699 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -35,6 +35,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
import com.android.systemui.doze.DozeHost;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.dagger.MediaModule;
import com.android.systemui.navigationbar.gestural.GestureModule;
import com.android.systemui.plugins.qs.QSFactory;
@@ -126,6 +127,7 @@
PowerManager powerManager,
BroadcastDispatcher broadcastDispatcher,
DemoModeController demoModeController,
+ DumpManager dumpManager,
@Main Handler mainHandler,
@Background Handler bgHandler) {
BatteryController bC = new BatteryControllerImpl(
@@ -134,6 +136,7 @@
powerManager,
broadcastDispatcher,
demoModeController,
+ dumpManager,
mainHandler,
bgHandler);
bC.init();
diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
index a252864..8b4aeef 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
@@ -78,23 +78,18 @@
reloadMeasures()
}
- private fun reloadAll(newReloadToken: Int) {
- if (reloadToken == newReloadToken) {
- return
- }
- reloadToken = newReloadToken
- reloadRes()
- reloadMeasures()
- }
-
fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) {
if (displayUniqueId != newDisplayUniqueId) {
displayUniqueId = newDisplayUniqueId
newReloadToken ?.let { reloadToken = it }
reloadRes()
reloadMeasures()
- } else {
- newReloadToken?.let { reloadAll(it) }
+ } else if (newReloadToken != null) {
+ if (reloadToken == newReloadToken) {
+ return
+ }
+ reloadToken = newReloadToken
+ reloadMeasures()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java
deleted file mode 100644
index 789ebc5..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.complication;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.widget.TextClock;
-
-import com.android.systemui.R;
-import com.android.systemui.dreams.complication.DoubleShadowTextHelper.ShadowInfo;
-
-import kotlin.Unit;
-
-/**
- * Extension of {@link TextClock} which draws two shadows on the text (ambient and key shadows)
- */
-public class DoubleShadowTextClock extends TextClock {
- private final DoubleShadowTextHelper mShadowHelper;
-
- public DoubleShadowTextClock(Context context) {
- this(context, null);
- }
-
- public DoubleShadowTextClock(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public DoubleShadowTextClock(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- final Resources resources = context.getResources();
- final ShadowInfo keyShadowInfo = new ShadowInfo(
- resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_radius),
- resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dx),
- resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dy),
- resources.getColor(R.color.dream_overlay_clock_key_text_shadow_color));
-
- final ShadowInfo ambientShadowInfo = new ShadowInfo(
- resources.getDimensionPixelSize(
- R.dimen.dream_overlay_clock_ambient_text_shadow_radius),
- resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dx),
- resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dy),
- resources.getColor(R.color.dream_overlay_clock_ambient_text_shadow_color));
- mShadowHelper = new DoubleShadowTextHelper(keyShadowInfo, ambientShadowInfo);
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- mShadowHelper.applyShadows(this, canvas, () -> {
- super.onDraw(canvas);
- return Unit.INSTANCE;
- });
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java
deleted file mode 100644
index cf7e312..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.complication;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-
-import kotlin.Unit;
-
-/**
- * Extension of {@link TextView} which draws two shadows on the text (ambient and key shadows}
- */
-public class DoubleShadowTextView extends TextView {
- private final DoubleShadowTextHelper mShadowHelper;
-
- public DoubleShadowTextView(Context context) {
- this(context, null);
- }
-
- public DoubleShadowTextView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public DoubleShadowTextView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- final Resources resources = context.getResources();
- final DoubleShadowTextHelper.ShadowInfo
- keyShadowInfo = new DoubleShadowTextHelper.ShadowInfo(
- resources.getDimensionPixelSize(
- R.dimen.dream_overlay_status_bar_key_text_shadow_radius),
- resources.getDimensionPixelSize(
- R.dimen.dream_overlay_status_bar_key_text_shadow_dx),
- resources.getDimensionPixelSize(
- R.dimen.dream_overlay_status_bar_key_text_shadow_dy),
- resources.getColor(R.color.dream_overlay_status_bar_key_text_shadow_color));
-
- final DoubleShadowTextHelper.ShadowInfo
- ambientShadowInfo = new DoubleShadowTextHelper.ShadowInfo(
- resources.getDimensionPixelSize(
- R.dimen.dream_overlay_status_bar_ambient_text_shadow_radius),
- resources.getDimensionPixelSize(
- R.dimen.dream_overlay_status_bar_ambient_text_shadow_dx),
- resources.getDimensionPixelSize(
- R.dimen.dream_overlay_status_bar_ambient_text_shadow_dy),
- resources.getColor(R.color.dream_overlay_status_bar_ambient_text_shadow_color));
- mShadowHelper = new DoubleShadowTextHelper(keyShadowInfo, ambientShadowInfo);
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- mShadowHelper.applyShadows(this, canvas, () -> {
- super.onDraw(canvas);
- return Unit.INSTANCE;
- });
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 26db3ee4..8c4d17d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -24,6 +24,7 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_UNLOCK_ANIMATION;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
@@ -802,6 +803,9 @@
} else if (trustAgentsEnabled
&& (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) {
return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
+ } else if (trustAgentsEnabled
+ && (strongAuth & SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED) != 0) {
+ return KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
} else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0
|| mUpdateMonitor.isFingerprintLockedOut())) {
return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 5612c22..29e2c1c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -76,6 +76,14 @@
return factory.create("NotifInterruptLog", 100);
}
+ /** Provides a logging buffer for notification rendering events. */
+ @Provides
+ @SysUISingleton
+ @NotificationRenderLog
+ public static LogBuffer provideNotificationRenderLogBuffer(LogBufferFactory factory) {
+ return factory.create("NotifRenderLog", 100);
+ }
+
/** Provides a logging buffer for all logs for lockscreen to shade transition events. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
new file mode 100644
index 0000000..8c8753a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for notification rendering logging. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface NotificationRenderLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 654c158..b36f33b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -202,6 +202,7 @@
* It will be called when the container is out of view.
*/
lateinit var updateUserVisibility: () -> Unit
+ lateinit var updateHostVisibility: () -> Unit
private val isReorderingAllowed: Boolean
get() = visualStabilityProvider.isReorderingAllowed
@@ -225,7 +226,13 @@
reorderAllPlayers(previousVisiblePlayerKey = null)
}
- keysNeedRemoval.forEach { removePlayer(it) }
+ keysNeedRemoval.forEach {
+ removePlayer(it)
+ }
+ if (keysNeedRemoval.size > 0) {
+ // Carousel visibility may need to be updated after late removals
+ updateHostVisibility()
+ }
keysNeedRemoval.clear()
// Update user visibility so that no extra impression will be logged when
@@ -247,6 +254,7 @@
receivedSmartspaceCardLatency: Int,
isSsReactivated: Boolean
) {
+ debugLogger.logMediaLoaded(key)
if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) {
// Log card received if a new resumable media card is added
MediaPlayerData.getMediaPlayer(key)?.let {
@@ -315,7 +323,7 @@
data: SmartspaceMediaData,
shouldPrioritize: Boolean
) {
- if (DEBUG) Log.d(TAG, "Loading Smartspace media update")
+ debugLogger.logRecommendationLoaded(key)
// Log the case where the hidden media carousel with the existed inactive resume
// media is shown by the Smartspace signal.
if (data.isActive) {
@@ -370,13 +378,21 @@
}
override fun onMediaDataRemoved(key: String) {
+ debugLogger.logMediaRemoved(key)
removePlayer(key)
}
override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
- if (DEBUG) Log.d(TAG, "My Smartspace media removal request is received")
+ debugLogger.logRecommendationRemoved(key, immediately)
if (immediately || isReorderingAllowed) {
- onMediaDataRemoved(key)
+ removePlayer(key)
+ if (!immediately) {
+ // Although it wasn't requested, we were able to process the removal
+ // immediately since reordering is allowed. So, notify hosts to update
+ if (this@MediaCarouselController::updateHostVisibility.isInitialized) {
+ updateHostVisibility()
+ }
+ }
} else {
keysNeedRemoval.add(key)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt
index 04ebd5a..b1018f9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt
@@ -40,6 +40,37 @@
"Removing control panel for $str1 from map without calling #onDestroy"
}
)
+
+ fun logMediaLoaded(key: String) = buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = key },
+ { "add player $str1" }
+ )
+
+ fun logMediaRemoved(key: String) = buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = key },
+ { "removing player $str1" }
+ )
+
+ fun logRecommendationLoaded(key: String) = buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = key },
+ { "add recommendation $str1" }
+ )
+
+ fun logRecommendationRemoved(key: String, immediately: Boolean) = buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = key
+ bool1 = immediately
+ },
+ { "removing recommendation $str1, immediate=$bool1" }
+ )
}
private const val TAG = "MediaCarouselCtlrLog"
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index c48271e..896fb47 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -1322,6 +1322,7 @@
println("externalListeners: ${mediaDataFilter.listeners}")
println("mediaEntries: $mediaEntries")
println("useMediaResumption: $useMediaResumption")
+ println("allowMediaRecommendations: $allowMediaRecommendations")
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 6baf6e1..e0b6d1f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -546,6 +546,11 @@
mediaCarouselController.updateUserVisibility = {
mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
}
+ mediaCarouselController.updateHostVisibility = {
+ mediaHosts.forEach {
+ it?.updateViewVisibility()
+ }
+ }
panelEventsEvents.registerListener(object : NotifPanelEvents.Listener {
override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index de2b5c9..8645922 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -167,7 +167,11 @@
}
}
- private fun updateViewVisibility() {
+ /**
+ * Updates this host's state based on the current media data's status, and invokes listeners if
+ * the visibility has changed
+ */
+ fun updateViewVisibility() {
state.visible = if (showsOnlyActiveMedia) {
mediaDataManager.hasActiveMediaOrRecommendation()
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
index 8e4ca5a..731e348 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
@@ -380,7 +380,7 @@
type: TYPE
) = traceSection("MediaViewController#attach") {
updateMediaViewControllerType(type)
- logger.logMediaLocation("attach", currentStartLocation, currentEndLocation)
+ logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation)
this.transitionLayout = transitionLayout
layoutController.attach(transitionLayout)
if (currentEndLocation == -1) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 7c4c64c..d605c1a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -955,9 +955,9 @@
updateDisabledForQuickstep(newConfig);
}
- if (DEBUG_MISSING_GESTURE) {
- Log.d(DEBUG_MISSING_GESTURE_TAG, "Config changed: config=" + newConfig);
- }
+ // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
+ Log.d(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig
+ + " lastReportedConfig=" + mLastReportedConfig);
mLastReportedConfig.updateFrom(newConfig);
updateDisplaySize();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index b6f6e93..624def6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -51,8 +51,6 @@
/** Quick settings tile: Hotspot **/
public class HotspotTile extends QSTileImpl<BooleanState> {
- private final Icon mEnabledStatic = ResourceIcon.get(R.drawable.ic_hotspot);
-
private final HotspotController mHotspotController;
private final DataSaverController mDataSaverController;
@@ -129,9 +127,6 @@
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
final boolean transientEnabling = arg == ARG_SHOW_TRANSIENT_ENABLING;
- if (state.slash == null) {
- state.slash = new SlashState();
- }
final int numConnectedDevices;
final boolean isTransient = transientEnabling || mHotspotController.isHotspotTransient();
@@ -150,13 +145,14 @@
isDataSaverEnabled = mDataSaverController.isDataSaverEnabled();
}
- state.icon = mEnabledStatic;
state.label = mContext.getString(R.string.quick_settings_hotspot_label);
state.isTransient = isTransient;
- state.slash.isSlashed = !state.value && !state.isTransient;
if (state.isTransient) {
state.icon = ResourceIcon.get(
- com.android.internal.R.drawable.ic_hotspot_transient_animation);
+ R.drawable.qs_hotspot_icon_search);
+ } else {
+ state.icon = ResourceIcon.get(state.value
+ ? R.drawable.qs_hotspot_icon_on : R.drawable.qs_hotspot_icon_off);
}
state.expandedAccessibilityClassName = Switch.class.getName();
state.contentDescription = state.label;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index f60e066..92f6690a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -81,8 +81,7 @@
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mBatteryController = batteryController;
- mUiModeManager = (UiModeManager) host.getUserContext().getSystemService(
- Context.UI_MODE_SERVICE);
+ mUiModeManager = host.getUserContext().getSystemService(UiModeManager.class);
mLocationController = locationController;
configurationController.observe(getLifecycle(), this);
batteryController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 1011a6d..d7e86b6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2230,7 +2230,8 @@
if (cancel) {
collapse(false /* delayed */, 1.0f /* speedUpFactor */);
} else {
- maybeVibrateOnOpening();
+ // Window never will receive touch events that typically trigger haptic on open.
+ maybeVibrateOnOpening(false /* openingWithTouch */);
fling(velocity > 1f ? 1000f * velocity : 0, true /* expand */);
}
onTrackingStopped(false);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
index c3f1e57..b4ce95c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
@@ -96,6 +96,7 @@
private float mMinExpandHeight;
private boolean mPanelUpdateWhenAnimatorEnds;
private final boolean mVibrateOnOpening;
+ private boolean mHasVibratedOnOpen = false;
protected boolean mIsLaunchAnimationRunning;
private int mFixedDuration = NO_FIXED_DURATION;
protected float mOverExpansion;
@@ -353,8 +354,8 @@
private void startOpening(MotionEvent event) {
updatePanelExpansionAndVisibility();
- maybeVibrateOnOpening();
-
+ // Reset at start so haptic can be triggered as soon as panel starts to open.
+ mHasVibratedOnOpen = false;
//TODO: keyguard opens QS a different way; log that too?
// Log the position of the swipe that opened the panel
@@ -368,9 +369,18 @@
.log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND);
}
- protected void maybeVibrateOnOpening() {
+ /**
+ * Maybe vibrate as panel is opened.
+ *
+ * @param openingWithTouch Whether the panel is being opened with touch. If the panel is instead
+ * being opened programmatically (such as by the open panel gesture), we always play haptic.
+ */
+ protected void maybeVibrateOnOpening(boolean openingWithTouch) {
if (mVibrateOnOpening) {
- mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+ if (!openingWithTouch || !mHasVibratedOnOpen) {
+ mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+ mHasVibratedOnOpen = true;
+ }
}
}
@@ -1371,6 +1381,9 @@
break;
case MotionEvent.ACTION_MOVE:
addMovement(event);
+ if (!isFullyCollapsed()) {
+ maybeVibrateOnOpening(true /* openingWithTouch */);
+ }
float h = y - mInitialExpandY;
// If the panel was collapsed when touching, we only need to check for the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
new file mode 100644
index 0000000..fe03b2a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.dagger.NotificationRenderLog
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.stack.NotificationSection
+import javax.inject.Inject
+
+/** Handles logging for the {NotificationRoundnessManager}. */
+class NotificationRoundnessLogger
+@Inject
+constructor(@NotificationRenderLog val buffer: LogBuffer) {
+
+ /** Called when the {NotificationRoundnessManager} updates the corners if the Notifications. */
+ fun onCornersUpdated(
+ view: ExpandableView?,
+ isFirstInSection: Boolean,
+ isLastInSection: Boolean,
+ topChanged: Boolean,
+ bottomChanged: Boolean
+ ) {
+ buffer.log(
+ TAG_ROUNDNESS,
+ INFO,
+ {
+ str1 = (view as? ExpandableNotificationRow)?.entry?.key
+ bool1 = isFirstInSection
+ bool2 = isLastInSection
+ bool3 = topChanged
+ bool4 = bottomChanged
+ },
+ {
+ "onCornersUpdated: " +
+ "entry=$str1 isFirstInSection=$bool1 isLastInSection=$bool2 " +
+ "topChanged=$bool3 bottomChanged=$bool4"
+ }
+ )
+ }
+
+ /** Called when we update the {NotificationRoundnessManager} with new sections. */
+ fun onSectionCornersUpdated(sections: Array<NotificationSection?>, anyChanged: Boolean) {
+ buffer.log(
+ TAG_ROUNDNESS,
+ INFO,
+ {
+ int1 = sections.size
+ bool1 = anyChanged
+ },
+ { "onSectionCornersUpdated: sections size=$int1 anyChanged=$bool1" }
+ )
+ }
+}
+
+private const val TAG_ROUNDNESS = "NotifRoundnessLogger"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index b589d9a..2015c87 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -19,12 +19,19 @@
import android.content.res.Resources;
import android.util.MathUtils;
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationRoundnessLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import java.io.PrintWriter;
import java.util.HashSet;
import javax.inject.Inject;
@@ -33,12 +40,16 @@
* A class that manages the roundness for notification views
*/
@SysUISingleton
-public class NotificationRoundnessManager {
+public class NotificationRoundnessManager implements Dumpable {
+
+ private static final String TAG = "NotificationRoundnessManager";
private final ExpandableView[] mFirstInSectionViews;
private final ExpandableView[] mLastInSectionViews;
private final ExpandableView[] mTmpFirstInSectionViews;
private final ExpandableView[] mTmpLastInSectionViews;
+ private final NotificationRoundnessLogger mNotifLogger;
+ private final DumpManager mDumpManager;
private boolean mExpanded;
private HashSet<ExpandableView> mAnimatedChildren;
private Runnable mRoundingChangedCallback;
@@ -53,12 +64,31 @@
@Inject
NotificationRoundnessManager(
- NotificationSectionsFeatureManager sectionsFeatureManager) {
+ NotificationSectionsFeatureManager sectionsFeatureManager,
+ NotificationRoundnessLogger notifLogger,
+ DumpManager dumpManager) {
int numberOfSections = sectionsFeatureManager.getNumberOfBuckets();
mFirstInSectionViews = new ExpandableView[numberOfSections];
mLastInSectionViews = new ExpandableView[numberOfSections];
mTmpFirstInSectionViews = new ExpandableView[numberOfSections];
mTmpLastInSectionViews = new ExpandableView[numberOfSections];
+ mNotifLogger = notifLogger;
+ mDumpManager = dumpManager;
+
+ mDumpManager.registerDumpable(TAG, this);
+ }
+
+ @Override
+ public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("mFirstInSectionViews: length=" + mFirstInSectionViews.length);
+ pw.println(dumpViews(mFirstInSectionViews));
+ pw.println("mLastInSectionViews: length=" + mLastInSectionViews.length);
+ pw.println(dumpViews(mFirstInSectionViews));
+ if (mTrackedHeadsUp != null) {
+ pw.println("trackedHeadsUp=" + mTrackedHeadsUp.getEntry());
+ }
+ pw.println("roundForPulsingViews=" + mRoundForPulsingViews);
+ pw.println("isClearAllInProgress=" + mIsClearAllInProgress);
}
public void updateView(ExpandableView view, boolean animate) {
@@ -95,6 +125,9 @@
view.setFirstInSection(isFirstInSection);
view.setLastInSection(isLastInSection);
+ mNotifLogger.onCornersUpdated(view, isFirstInSection,
+ isLastInSection, topChanged, bottomChanged);
+
return (isFirstInSection || isLastInSection) && (topChanged || bottomChanged);
}
@@ -184,6 +217,7 @@
if (isLastInSection(view) && !top) {
return 1.0f;
}
+
if (view == mTrackedHeadsUp) {
// If we're pushing up on a headsup the appear fraction is < 0 and it needs to still be
// rounded.
@@ -220,6 +254,8 @@
if (anyChanged) {
mRoundingChangedCallback.run();
}
+
+ mNotifLogger.onSectionCornersUpdated(sections, anyChanged);
}
private boolean handleRemovedOldViews(NotificationSection[] sections,
@@ -296,4 +332,36 @@
public void setShouldRoundPulsingViews(boolean shouldRoundPulsingViews) {
mRoundForPulsingViews = shouldRoundPulsingViews;
}
+
+ private String dumpViews(ExpandableView[] views) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < views.length; i++) {
+ if (views[i] == null) continue;
+
+ sb.append("\t")
+ .append("[").append(i).append("] ")
+ .append("isPinned=").append(views[i].isPinned()).append(" ")
+ .append("isFirstInSection=").append(views[i].isFirstInSection()).append(" ")
+ .append("isLastInSection=").append(views[i].isLastInSection()).append(" ");
+
+ if (views[i] instanceof ExpandableNotificationRow) {
+ sb.append("entry=");
+ dumpEntry(((ExpandableNotificationRow) views[i]).getEntry(), sb);
+ }
+
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+
+ private void dumpEntry(NotificationEntry entry, StringBuilder sb) {
+ sb.append("NotificationEntry{key=").append(entry.getKey()).append(" ");
+
+ if (entry.getSection() != null) {
+ sb.append(" section=")
+ .append(entry.getSection().getLabel());
+ }
+
+ sb.append("}");
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 753e940..149ed0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -19,14 +19,17 @@
import android.annotation.Nullable;
import android.view.View;
-import com.android.systemui.Dumpable;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
-public interface BatteryController extends DemoMode, Dumpable,
+/**
+ * Controller for battery related information, including the charge level, power save mode,
+ * and time remaining information
+ */
+public interface BatteryController extends DemoMode,
CallbackController<BatteryStateChangeCallback> {
/**
* Prints the current state of the {@link BatteryController} to the given {@link PrintWriter}.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 33ddf7e..c7ad767 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -38,11 +38,13 @@
import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.settingslib.fuelgauge.Estimate;
import com.android.settingslib.utils.PowerUtil;
+import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.util.Assert;
@@ -56,7 +58,8 @@
* Default implementation of a {@link BatteryController}. This controller monitors for battery
* level change events that are broadcasted by the system.
*/
-public class BatteryControllerImpl extends BroadcastReceiver implements BatteryController {
+public class BatteryControllerImpl extends BroadcastReceiver implements BatteryController,
+ Dumpable {
private static final String TAG = "BatteryController";
private static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
@@ -70,6 +73,7 @@
private final ArrayList<EstimateFetchCompletion> mFetchCallbacks = new ArrayList<>();
private final PowerManager mPowerManager;
private final DemoModeController mDemoModeController;
+ private final DumpManager mDumpManager;
private final Handler mMainHandler;
private final Handler mBgHandler;
protected final Context mContext;
@@ -101,6 +105,7 @@
PowerManager powerManager,
BroadcastDispatcher broadcastDispatcher,
DemoModeController demoModeController,
+ DumpManager dumpManager,
@Main Handler mainHandler,
@Background Handler bgHandler) {
mContext = context;
@@ -110,6 +115,7 @@
mEstimates = enhancedEstimates;
mBroadcastDispatcher = broadcastDispatcher;
mDemoModeController = demoModeController;
+ mDumpManager = dumpManager;
}
private void registerReceiver() {
@@ -134,6 +140,7 @@
}
}
mDemoModeController.addCallback(this);
+ mDumpManager.registerDumpable(TAG, this);
updatePowerSave();
updateEstimateInBackground();
}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 27746c0..00ed3d6 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -36,6 +36,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
import com.android.systemui.doze.DozeHost;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.gestural.GestureModule;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -116,9 +117,12 @@
static BatteryController provideBatteryController(Context context,
EnhancedEstimates enhancedEstimates, PowerManager powerManager,
BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController,
+ DumpManager dumpManager,
@Main Handler mainHandler, @Background Handler bgHandler) {
BatteryController bC = new BatteryControllerImpl(context, enhancedEstimates, powerManager,
- broadcastDispatcher, demoModeController, mainHandler, bgHandler);
+ broadcastDispatcher, demoModeController,
+ dumpManager,
+ mainHandler, bgHandler);
bC.init();
return bC;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index df10dfe..5a26d05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -1005,18 +1005,13 @@
assertEquals(new Size(3, 3), resDelegate.getTopRoundedSize());
assertEquals(new Size(4, 4), resDelegate.getBottomRoundedSize());
- setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
- getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px)
- /* roundedTopDrawable */,
- getTestsDrawable(com.android.systemui.tests.R.drawable.rounded5px)
- /* roundedBottomDrawable */,
- 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning*/);
+ doReturn(2f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
mDisplayInfo.rotation = Surface.ROTATION_270;
mScreenDecorations.onConfigurationChanged(null);
- assertEquals(new Size(4, 4), resDelegate.getTopRoundedSize());
- assertEquals(new Size(5, 5), resDelegate.getBottomRoundedSize());
+ assertEquals(new Size(6, 6), resDelegate.getTopRoundedSize());
+ assertEquals(new Size(8, 8), resDelegate.getBottomRoundedSize());
}
@Test
@@ -1293,51 +1288,6 @@
}
@Test
- public void testOnDisplayChanged_hwcLayer() {
- setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
- null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
- 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
- final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
- decorationSupport.format = PixelFormat.R_8;
- doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();
-
- // top cutout
- mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
-
- mScreenDecorations.start();
-
- final ScreenDecorHwcLayer hwcLayer = mScreenDecorations.mScreenDecorHwcLayer;
- spyOn(hwcLayer);
- doReturn(mDisplay).when(hwcLayer).getDisplay();
-
- mScreenDecorations.mDisplayListener.onDisplayChanged(1);
-
- verify(hwcLayer, times(1)).onDisplayChanged(any());
- }
-
- @Test
- public void testOnDisplayChanged_nonHwcLayer() {
- setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
- null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
- 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
-
- // top cutout
- mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
-
- mScreenDecorations.start();
-
- final ScreenDecorations.DisplayCutoutView cutoutView = (ScreenDecorations.DisplayCutoutView)
- mScreenDecorations.getOverlayView(R.id.display_cutout);
- assertNotNull(cutoutView);
- spyOn(cutoutView);
- doReturn(mDisplay).when(cutoutView).getDisplay();
-
- mScreenDecorations.mDisplayListener.onDisplayChanged(1);
-
- verify(cutoutView, times(1)).onDisplayChanged(any());
- }
-
- @Test
public void testHasSameProvidersWithNullOverlays() {
setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
index f933361..93a1868 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
@@ -24,12 +24,11 @@
import androidx.test.filters.SmallTest
import com.android.internal.R as InternalR
import com.android.systemui.R as SystemUIR
-import com.android.systemui.tests.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.tests.R
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
-
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@@ -102,14 +101,11 @@
assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize)
assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize)
- setupResources(radius = 100,
- roundedTopDrawable = getTestsDrawable(R.drawable.rounded4px),
- roundedBottomDrawable = getTestsDrawable(R.drawable.rounded5px))
-
+ roundedCornerResDelegate.physicalPixelDisplaySizeRatio = 2f
roundedCornerResDelegate.updateDisplayUniqueId(null, 1)
- assertEquals(Size(4, 4), roundedCornerResDelegate.topRoundedSize)
- assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize)
+ assertEquals(Size(6, 6), roundedCornerResDelegate.topRoundedSize)
+ assertEquals(Size(8, 8), roundedCornerResDelegate.bottomRoundedSize)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
index 5a50a9f..5dd1cfc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.animation.TransitionLayout
@@ -78,6 +79,7 @@
@Mock lateinit var mediaViewController: MediaViewController
@Mock lateinit var smartspaceMediaData: SmartspaceMediaData
@Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
+ @Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener>
private val clock = FakeSystemClock()
private lateinit var mediaCarouselController: MediaCarouselController
@@ -102,6 +104,8 @@
debugLogger
)
verify(mediaDataManager).addListener(capture(listener))
+ verify(visualStabilityProvider)
+ .addPersistentReorderingAllowedListener(capture(visualStabilityCallback))
whenever(mediaControlPanelFactory.get()).thenReturn(mediaPlayer)
whenever(mediaPlayer.mediaViewController).thenReturn(mediaViewController)
whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData)
@@ -374,4 +378,28 @@
playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
assertEquals(playerIndex, 0)
}
+
+ @Test
+ fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() {
+ var result = false
+ mediaCarouselController.updateHostVisibility = { result = true }
+
+ whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true)
+ listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false)
+
+ assertEquals(true, result)
+ }
+
+ @Test
+ fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() {
+ var result = false
+ mediaCarouselController.updateHostVisibility = { result = true }
+
+ whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false)
+ listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false)
+ assertEquals(false, result)
+
+ visualStabilityCallback.value.onReorderingAllowed()
+ assertEquals(true, result)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
index b86713d..451e911 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
@@ -39,6 +39,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.HotspotController;
@@ -122,4 +123,40 @@
.isEqualTo(mContext.getString(R.string.wifitrackerlib_admin_restricted_network));
mockitoSession.finishMocking();
}
+
+ @Test
+ public void testIcon_whenDisabled_isOffState() {
+ QSTile.BooleanState state = new QSTile.BooleanState();
+ when(mHotspotController.isHotspotTransient()).thenReturn(false);
+ when(mHotspotController.isHotspotEnabled()).thenReturn(false);
+
+ mTile.handleUpdateState(state, /* arg= */ null);
+
+ assertThat(state.icon)
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_off));
+ }
+
+ @Test
+ public void testIcon_whenTransient_isSearchState() {
+ QSTile.BooleanState state = new QSTile.BooleanState();
+ when(mHotspotController.isHotspotTransient()).thenReturn(true);
+ when(mHotspotController.isHotspotEnabled()).thenReturn(true);
+
+ mTile.handleUpdateState(state, /* arg= */ null);
+
+ assertThat(state.icon)
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_search));
+ }
+
+ @Test
+ public void testIcon_whenEnabled_isOnState() {
+ QSTile.BooleanState state = new QSTile.BooleanState();
+ when(mHotspotController.isHotspotTransient()).thenReturn(false);
+ when(mHotspotController.isHotspotEnabled()).thenReturn(true);
+
+ mTile.handleUpdateState(state, /* arg= */ null);
+
+ assertThat(state.icon)
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_on));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
index ea70c26..0c070da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
@@ -21,9 +21,9 @@
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Handler
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.R
@@ -51,28 +51,17 @@
@SmallTest
class UiModeNightTileTest : SysuiTestCase() {
- @Mock
- private lateinit var mockContext: Context
- @Mock
- private lateinit var uiModeManager: UiModeManager
- @Mock
- private lateinit var resources: Resources
- @Mock
- private lateinit var qsLogger: QSLogger
- @Mock
- private lateinit var qsHost: QSTileHost
- @Mock
- private lateinit var metricsLogger: MetricsLogger
- @Mock
- private lateinit var statusBarStateController: StatusBarStateController
- @Mock
- private lateinit var activityStarter: ActivityStarter
- @Mock
- private lateinit var configurationController: ConfigurationController
- @Mock
- private lateinit var batteryController: BatteryController
- @Mock
- private lateinit var locationController: LocationController
+ @Mock private lateinit var mockContext: Context
+ @Mock private lateinit var uiModeManager: UiModeManager
+ @Mock private lateinit var resources: Resources
+ @Mock private lateinit var qsLogger: QSLogger
+ @Mock private lateinit var qsHost: QSTileHost
+ @Mock private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var configurationController: ConfigurationController
+ @Mock private lateinit var batteryController: BatteryController
+ @Mock private lateinit var locationController: LocationController
private val uiEventLogger = UiEventLoggerFake()
private val falsingManager = FalsingManagerFake()
@@ -85,7 +74,7 @@
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
configuration = Configuration()
- mContext.addMockSystemService(Context.UI_MODE_SERVICE, uiModeManager)
+ mContext.addMockSystemService(UiModeManager::class.java, uiModeManager)
`when`(qsHost.context).thenReturn(mockContext)
`when`(qsHost.userContext).thenReturn(mContext)
@@ -93,7 +82,8 @@
`when`(resources.configuration).thenReturn(configuration)
`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
- tile = UiModeNightTile(
+ tile =
+ UiModeNightTile(
qsHost,
testableLooper.looper,
Handler(testableLooper.looper),
@@ -104,7 +94,8 @@
qsLogger,
configurationController,
batteryController,
- locationController)
+ locationController
+ )
}
@Test
@@ -115,7 +106,7 @@
tile.handleUpdateState(state, /* arg= */ null)
assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_on))
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_on))
}
@Test
@@ -126,7 +117,7 @@
tile.handleUpdateState(state, /* arg= */ null)
assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_off))
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_off))
}
private fun setNightModeOn() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index d3c1dc9..a95a49c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.stack;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -32,8 +34,10 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationRoundnessLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -57,6 +61,7 @@
private Runnable mRoundnessCallback = mock(Runnable.class);
private ExpandableNotificationRow mFirst;
private ExpandableNotificationRow mSecond;
+ private NotificationRoundnessLogger mLogger = mock(NotificationRoundnessLogger.class);
private float mSmallRadiusRatio;
@Before
@@ -66,7 +71,9 @@
mSmallRadiusRatio = resources.getDimension(R.dimen.notification_corner_radius_small)
/ resources.getDimension(R.dimen.notification_corner_radius);
mRoundnessManager = new NotificationRoundnessManager(
- new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext));
+ new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext),
+ mLogger,
+ mock(DumpManager.class));
allowTestableLooperAsMainThread();
NotificationTestHelper testHelper = new NotificationTestHelper(
mContext,
@@ -337,6 +344,20 @@
Assert.assertTrue(mSecond.isLastInSection());
}
+ @Test
+ public void testLoggingOnRoundingUpdate() {
+ NotificationSection[] sections = new NotificationSection[]{
+ createSection(mFirst, mSecond),
+ createSection(null, null)
+ };
+ mRoundnessManager.updateRoundedChildren(sections);
+ verify(mLogger).onSectionCornersUpdated(sections, /*anyChanged=*/ true);
+ verify(mLogger, atLeast(1)).onCornersUpdated(eq(mFirst), anyBoolean(),
+ anyBoolean(), anyBoolean(), anyBoolean());
+ verify(mLogger, atLeast(1)).onCornersUpdated(eq(mSecond), anyBoolean(),
+ anyBoolean(), anyBoolean(), anyBoolean());
+ }
+
private NotificationSection createSection(ExpandableNotificationRow first,
ExpandableNotificationRow last) {
NotificationSection section = mock(NotificationSection.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index fda80a2..43d0fe9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -42,6 +42,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -80,6 +81,7 @@
mPowerManager,
mBroadcastDispatcher,
mDemoModeController,
+ mock(DumpManager.class),
new Handler(),
new Handler());
// Can throw if updateEstimate is called on the main thread
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 5c84a62..ae65dcb 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -1,6 +1,9 @@
{
"presubmit": [
{
+ "name": "CtsLocationFineTestCases"
+ },
+ {
"name": "CtsLocationCoarseTestCases"
},
{
@@ -66,10 +69,5 @@
],
"file_patterns": ["ClipboardService\\.java"]
}
- ],
- "postsubmit": [
- {
- "name": "CtsLocationFineTestCases"
- }
]
}
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index f26d9f9..76cac93 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -24,6 +24,7 @@
import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE;
import static android.net.vcn.VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED;
import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
+import static android.telephony.SubscriptionManager.isValidSubscriptionId;
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
@@ -167,6 +168,10 @@
static final String VCN_CONFIG_FILE =
new File(Environment.getDataSystemDirectory(), "vcn/configs.xml").getPath();
+ // TODO(b/176956496): Directly use CarrierServiceBindHelper.UNBIND_DELAY_MILLIS
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30);
+
/* Binder context for this service */
@NonNull private final Context mContext;
@NonNull private final Dependencies mDeps;
@@ -360,15 +365,12 @@
/** Notifies the VcnManagementService that external dependencies can be set up. */
public void systemReady() {
- // Always run on the handler thread to ensure consistency.
- mHandler.post(() -> {
- mNetworkProvider.register();
- mContext.getSystemService(ConnectivityManager.class)
- .registerNetworkCallback(
- new NetworkRequest.Builder().clearCapabilities().build(),
- mTrackingNetworkCallback);
- mTelephonySubscriptionTracker.register();
- });
+ mNetworkProvider.register();
+ mContext.getSystemService(ConnectivityManager.class)
+ .registerNetworkCallback(
+ new NetworkRequest.Builder().clearCapabilities().build(),
+ mTrackingNetworkCallback);
+ mTelephonySubscriptionTracker.register();
}
private void enforcePrimaryUser() {
@@ -509,15 +511,22 @@
if (!mVcns.containsKey(subGrp)) {
startVcnLocked(subGrp, entry.getValue());
}
+
+ // Cancel any scheduled teardowns for active subscriptions
+ mHandler.removeCallbacksAndMessages(mVcns.get(subGrp));
}
}
- // Schedule teardown of any VCN instances that have lost carrier privileges
+ // Schedule teardown of any VCN instances that have lost carrier privileges (after a
+ // delay)
for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) {
final ParcelUuid subGrp = entry.getKey();
final VcnConfig config = mConfigs.get(subGrp);
final boolean isActiveSubGrp = isActiveSubGroup(subGrp, snapshot);
+ final boolean isValidActiveDataSubIdNotInVcnSubGrp =
+ isValidSubscriptionId(snapshot.getActiveDataSubscriptionId())
+ && !isActiveSubGroup(subGrp, snapshot);
// TODO(b/193687515): Support multiple VCNs active at the same time
if (config == null
@@ -527,12 +536,31 @@
final ParcelUuid uuidToTeardown = subGrp;
final Vcn instanceToTeardown = entry.getValue();
- stopVcnLocked(uuidToTeardown);
+ // TODO(b/193687515): Support multiple VCNs active at the same time
+ // If directly switching to a subscription not in the current group,
+ // teardown immediately to prevent other subscription's network from being
+ // outscored by the VCN. Otherwise, teardown after a delay to ensure that
+ // SIM profile switches do not trigger the VCN to cycle.
+ final long teardownDelayMs =
+ isValidActiveDataSubIdNotInVcnSubGrp
+ ? 0
+ : CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS;
+ mHandler.postDelayed(() -> {
+ synchronized (mLock) {
+ // Guard against case where this is run after a old instance was
+ // torn down, and a new instance was started. Verify to ensure
+ // correct instance is torn down. This could happen as a result of a
+ // Carrier App manually removing/adding a VcnConfig.
+ if (mVcns.get(uuidToTeardown) == instanceToTeardown) {
+ stopVcnLocked(uuidToTeardown);
- // TODO(b/181789060): invoke asynchronously after Vcn notifies
- // through VcnCallback
- notifyAllPermissionedStatusCallbacksLocked(
- uuidToTeardown, VCN_STATUS_CODE_INACTIVE);
+ // TODO(b/181789060): invoke asynchronously after Vcn notifies
+ // through VcnCallback
+ notifyAllPermissionedStatusCallbacksLocked(
+ uuidToTeardown, VCN_STATUS_CODE_INACTIVE);
+ }
+ }
+ }, instanceToTeardown, teardownDelayMs);
} else {
// If this VCN's status has not changed, update it with the new snapshot
entry.getValue().updateSubscriptionSnapshot(mLastSnapshot);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d84b0e8..86918aa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -668,10 +668,17 @@
// we still create this new offload queue, but never ever put anything on it.
final boolean mEnableOffloadQueue;
- final BroadcastQueue mFgBroadcastQueue;
- final BroadcastQueue mBgBroadcastQueue;
- final BroadcastQueue mBgOffloadBroadcastQueue;
- final BroadcastQueue mFgOffloadBroadcastQueue;
+ /**
+ * Flag indicating if we should use {@link BroadcastQueueModernImpl} instead
+ * of the default {@link BroadcastQueueImpl}.
+ */
+ final boolean mEnableModernQueue;
+
+ static final int BROADCAST_QUEUE_FG = 0;
+ static final int BROADCAST_QUEUE_BG = 1;
+ static final int BROADCAST_QUEUE_BG_OFFLOAD = 2;
+ static final int BROADCAST_QUEUE_FG_OFFLOAD = 3;
+
// Convenient for easy iteration over the queues. Foreground is first
// so that dispatch of foreground broadcasts gets precedence.
final BroadcastQueue[] mBroadcastQueues;
@@ -693,12 +700,16 @@
}
BroadcastQueue broadcastQueueForFlags(int flags, Object cookie) {
+ if (mEnableModernQueue) {
+ return mBroadcastQueues[0];
+ }
+
if (isOnFgOffloadQueue(flags)) {
if (DEBUG_BROADCAST_BACKGROUND) {
Slog.i(TAG_BROADCAST,
"Broadcast intent " + cookie + " on foreground offload queue");
}
- return mFgOffloadBroadcastQueue;
+ return mBroadcastQueues[BROADCAST_QUEUE_FG_OFFLOAD];
}
if (isOnBgOffloadQueue(flags)) {
@@ -706,14 +717,15 @@
Slog.i(TAG_BROADCAST,
"Broadcast intent " + cookie + " on background offload queue");
}
- return mBgOffloadBroadcastQueue;
+ return mBroadcastQueues[BROADCAST_QUEUE_BG_OFFLOAD];
}
final boolean isFg = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
if (DEBUG_BROADCAST_BACKGROUND) Slog.i(TAG_BROADCAST,
"Broadcast intent " + cookie + " on "
+ (isFg ? "foreground" : "background") + " queue");
- return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue;
+ return (isFg) ? mBroadcastQueues[BROADCAST_QUEUE_FG]
+ : mBroadcastQueues[BROADCAST_QUEUE_BG];
}
private volatile int mDeviceOwnerUid = INVALID_UID;
@@ -2366,9 +2378,8 @@
mPendingStartActivityUids = new PendingStartActivityUids();
mUseFifoUiScheduling = false;
mEnableOffloadQueue = false;
+ mEnableModernQueue = false;
mBroadcastQueues = new BroadcastQueue[0];
- mFgBroadcastQueue = mBgBroadcastQueue = mBgOffloadBroadcastQueue =
- mFgOffloadBroadcastQueue = null;
mComponentAliasResolver = new ComponentAliasResolver(this);
}
@@ -2424,20 +2435,23 @@
mEnableOffloadQueue = SystemProperties.getBoolean(
"persist.device_config.activity_manager_native_boot.offload_queue_enabled", true);
+ mEnableModernQueue = SystemProperties.getBoolean(
+ "persist.device_config.activity_manager_native_boot.modern_queue_enabled", false);
- mBroadcastQueues = new BroadcastQueue[4];
- mFgBroadcastQueue = new BroadcastQueueImpl(this, mHandler,
- "foreground", foreConstants, false, ProcessList.SCHED_GROUP_DEFAULT);
- mBgBroadcastQueue = new BroadcastQueueImpl(this, mHandler,
- "background", backConstants, true, ProcessList.SCHED_GROUP_BACKGROUND);
- mBgOffloadBroadcastQueue = new BroadcastQueueImpl(this, mHandler,
- "offload_bg", offloadConstants, true, ProcessList.SCHED_GROUP_BACKGROUND);
- mFgOffloadBroadcastQueue = new BroadcastQueueImpl(this, mHandler,
- "offload_fg", foreConstants, true, ProcessList.SCHED_GROUP_BACKGROUND);
- mBroadcastQueues[0] = mFgBroadcastQueue;
- mBroadcastQueues[1] = mBgBroadcastQueue;
- mBroadcastQueues[2] = mBgOffloadBroadcastQueue;
- mBroadcastQueues[3] = mFgOffloadBroadcastQueue;
+ if (mEnableModernQueue) {
+ mBroadcastQueues = new BroadcastQueue[1];
+ mBroadcastQueues[0] = new BroadcastQueueModernImpl(this, mHandler, foreConstants);
+ } else {
+ mBroadcastQueues = new BroadcastQueue[4];
+ mBroadcastQueues[BROADCAST_QUEUE_FG] = new BroadcastQueueImpl(this, mHandler,
+ "foreground", foreConstants, false, ProcessList.SCHED_GROUP_DEFAULT);
+ mBroadcastQueues[BROADCAST_QUEUE_BG] = new BroadcastQueueImpl(this, mHandler,
+ "background", backConstants, true, ProcessList.SCHED_GROUP_BACKGROUND);
+ mBroadcastQueues[BROADCAST_QUEUE_BG_OFFLOAD] = new BroadcastQueueImpl(this, mHandler,
+ "offload_bg", offloadConstants, true, ProcessList.SCHED_GROUP_BACKGROUND);
+ mBroadcastQueues[BROADCAST_QUEUE_FG_OFFLOAD] = new BroadcastQueueImpl(this, mHandler,
+ "offload_fg", foreConstants, true, ProcessList.SCHED_GROUP_BACKGROUND);
+ }
mServices = new ActiveServices(this);
mCpHelper = new ContentProviderHelper(this, true);
@@ -8913,6 +8927,7 @@
* @param incrementalMetrics metrics for apps installed on Incremental.
* @param errorId a unique id to append to the dropbox headers.
*/
+ @SuppressWarnings("DoNotCall") // Ignore warning for synchronous to call to worker.run()
public void addErrorToDropBox(String eventType,
ProcessRecord process, String processName, String activityShortComponentName,
String parentShortComponentName, ProcessRecord parentProcess,
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 3299ee3..ceff67e 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -44,6 +44,7 @@
import static com.android.server.am.ActivityManagerService.getKsmInfo;
import static com.android.server.am.ActivityManagerService.stringifyKBSize;
import static com.android.server.am.LowMemDetector.ADJ_MEM_FACTOR_NOTHING;
+import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_NONE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
import static com.android.server.wm.ActivityTaskManagerService.DUMP_ACTIVITIES_CMD;
@@ -1047,17 +1048,7 @@
}
trimMemoryUiHiddenIfNecessaryLSP(app);
if (curProcState >= ActivityManager.PROCESS_STATE_HOME && !app.isKilledByAm()) {
- if (trimMemoryLevel < curLevel[0] && (thread = app.getThread()) != null) {
- try {
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
- Slog.v(TAG_OOM_ADJ,
- "Trimming memory of " + app.processName
- + " to " + curLevel[0]);
- }
- thread.scheduleTrimMemory(curLevel[0]);
- } catch (RemoteException e) {
- }
- }
+ scheduleTrimMemoryLSP(app, curLevel[0], "Trimming memory of ");
profile.setTrimMemoryLevel(curLevel[0]);
step[0]++;
if (step[0] >= actualFactor) {
@@ -1073,31 +1064,11 @@
}
} else if (curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
&& !app.isKilledByAm()) {
- if (trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND
- && (thread = app.getThread()) != null) {
- try {
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
- Slog.v(TAG_OOM_ADJ,
- "Trimming memory of heavy-weight " + app.processName
- + " to " + ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
- }
- thread.scheduleTrimMemory(
- ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
- } catch (RemoteException e) {
- }
- }
+ scheduleTrimMemoryLSP(app, ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
+ "Trimming memory of heavy-weight ");
profile.setTrimMemoryLevel(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
} else {
- if (trimMemoryLevel < fgTrimLevel && (thread = app.getThread()) != null) {
- try {
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
- Slog.v(TAG_OOM_ADJ, "Trimming memory of fg " + app.processName
- + " to " + fgTrimLevel);
- }
- thread.scheduleTrimMemory(fgTrimLevel);
- } catch (RemoteException e) {
- }
- }
+ scheduleTrimMemoryLSP(app, fgTrimLevel, "Trimming memory of fg ");
profile.setTrimMemoryLevel(fgTrimLevel);
}
});
@@ -1128,22 +1099,28 @@
// If this application is now in the background and it
// had done UI, then give it the special trim level to
// have it free UI resources.
- final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
- IApplicationThread thread;
- if (app.mProfile.getTrimMemoryLevel() < level && (thread = app.getThread()) != null) {
- try {
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
- Slog.v(TAG_OOM_ADJ, "Trimming memory of bg-ui "
- + app.processName + " to " + level);
- }
- thread.scheduleTrimMemory(level);
- } catch (RemoteException e) {
- }
- }
+ scheduleTrimMemoryLSP(app, ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN,
+ "Trimming memory of bg-ui ");
app.mProfile.setPendingUiClean(false);
}
}
+ @GuardedBy({"mService", "mProcLock"})
+ private void scheduleTrimMemoryLSP(ProcessRecord app, int level, String msg) {
+ IApplicationThread thread;
+ if (app.mProfile.getTrimMemoryLevel() < level && (thread = app.getThread()) != null) {
+ try {
+ if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
+ Slog.v(TAG_OOM_ADJ, msg + app.processName + " to " + level);
+ }
+ mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app,
+ OOM_ADJ_REASON_NONE);
+ thread.scheduleTrimMemory(level);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
@GuardedBy("mProcLock")
long getLowRamTimeSinceIdleLPr(long now) {
return mLowRamTimeSinceLastIdle + (mLowRamStartTime > 0 ? (now - mLowRamStartTime) : 0);
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
new file mode 100644
index 0000000..c18c65e
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.android.server.am.BroadcastQueue.checkState;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UptimeMillisLong;
+import android.os.UserHandle;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayDeque;
+
+/**
+ * Queue of pending {@link BroadcastRecord} entries intended for delivery to a
+ * specific process.
+ * <p>
+ * Each queue has a concept of being "runnable at" a particular time in the
+ * future, which supports arbitrarily pausing or delaying delivery on a
+ * per-process basis.
+ * <p>
+ * Internally each queue consists of a pending broadcasts which are waiting to
+ * be dispatched, and a single active broadcast which is currently being
+ * dispatched.
+ */
+class BroadcastProcessQueue implements Comparable<BroadcastProcessQueue> {
+ /**
+ * Default delay to apply to background broadcasts, giving a chance for
+ * debouncing of rapidly changing events.
+ */
+ // TODO: shift hard-coded defaults to BroadcastConstants
+ private static final long DELAY_DEFAULT_MILLIS = 10_000;
+
+ /**
+ * Default delay to apply to broadcasts targeting cached applications.
+ */
+ // TODO: shift hard-coded defaults to BroadcastConstants
+ private static final long DELAY_CACHED_MILLIS = 30_000;
+
+ final @NonNull String processName;
+ final int uid;
+
+ /**
+ * Linked list connection to another process under this {@link #uid} which
+ * has a different {@link #processName}.
+ */
+ @Nullable BroadcastProcessQueue next;
+
+ /**
+ * Currently known details about the target process; typically undefined
+ * when the process isn't actively running.
+ */
+ @Nullable ProcessRecord app;
+
+ /**
+ * Ordered collection of broadcasts that are waiting to be dispatched to
+ * this process, as a pair of {@link BroadcastRecord} and the index into
+ * {@link BroadcastRecord#receivers} that represents the receiver.
+ */
+ private final ArrayDeque<SomeArgs> mPending = new ArrayDeque<>();
+
+ /**
+ * Broadcast actively being dispatched to this process.
+ */
+ private @Nullable BroadcastRecord mActive;
+
+ /**
+ * Receiver actively being dispatched to in this process. This is an index
+ * into the {@link BroadcastRecord#receivers} list of {@link #mActive}.
+ */
+ private int mActiveIndex;
+
+ private int mCountForeground;
+ private int mCountOrdered;
+ private int mCountAlarm;
+
+ private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE;
+ private boolean mRunnableAtInvalidated;
+
+ private boolean mProcessCached;
+
+ public BroadcastProcessQueue(@NonNull String processName, int uid) {
+ this.processName = processName;
+ this.uid = uid;
+ }
+
+ /**
+ * Enqueue the given broadcast to be dispatched to this process at some
+ * future point in time. The target receiver is indicated by the given index
+ * into {@link BroadcastRecord#receivers}.
+ */
+ public void enqueueBroadcast(@NonNull BroadcastRecord record, int recordIndex) {
+ // Detect situations where the incoming broadcast should cause us to
+ // recalculate when we'll be runnable
+ if (mPending.isEmpty()) {
+ invalidateRunnableAt();
+ }
+ if (record.isForeground()) {
+ mCountForeground++;
+ invalidateRunnableAt();
+ }
+ if (record.ordered) {
+ mCountOrdered++;
+ invalidateRunnableAt();
+ }
+ if (record.alarm) {
+ mCountAlarm++;
+ invalidateRunnableAt();
+ }
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = record;
+ args.argi1 = recordIndex;
+ mPending.addLast(args);
+ }
+
+ /**
+ * Update if this process is in the "cached" state, typically signaling that
+ * broadcast dispatch should be paused or delayed.
+ */
+ public void setProcessCached(boolean cached) {
+ if (mProcessCached != cached) {
+ mProcessCached = cached;
+ invalidateRunnableAt();
+ }
+ }
+
+ /**
+ * Return if we know of an actively running "warm" process for this queue.
+ */
+ public boolean isProcessWarm() {
+ return (app != null) && (app.getThread() != null) && !app.isKilled();
+ }
+
+ public int getPreferredSchedulingGroupLocked() {
+ if (mCountForeground > 0 || mCountOrdered > 0 || mCountAlarm > 0) {
+ // We have an important broadcast somewhere down the queue, so
+ // boost priority until we drain them all
+ return ProcessList.SCHED_GROUP_DEFAULT;
+ } else if ((mActive != null)
+ && (mActive.isForeground() || mActive.ordered || mActive.alarm)) {
+ // We have an important broadcast right now, so boost priority
+ return ProcessList.SCHED_GROUP_DEFAULT;
+ } else {
+ return ProcessList.SCHED_GROUP_BACKGROUND;
+ }
+ }
+
+ /**
+ * Set the currently active broadcast to the next pending broadcast.
+ */
+ public void makeActiveNextPending() {
+ // TODO: what if the next broadcast isn't runnable yet?
+ checkState(isRunnable(), "isRunnable");
+ final SomeArgs next = mPending.removeFirst();
+ mActive = (BroadcastRecord) next.arg1;
+ mActiveIndex = next.argi1;
+ next.recycle();
+ if (mActive.isForeground()) {
+ mCountForeground--;
+ }
+ if (mActive.ordered) {
+ mCountOrdered--;
+ }
+ if (mActive.alarm) {
+ mCountAlarm--;
+ }
+ invalidateRunnableAt();
+ }
+
+ /**
+ * Set the currently running broadcast to be idle.
+ */
+ public void makeActiveIdle() {
+ mActive = null;
+ mActiveIndex = 0;
+ }
+
+ public void setActiveDeliveryState(int deliveryState) {
+ checkState(isActive(), "isActive");
+ mActive.setDeliveryState(mActiveIndex, deliveryState);
+ }
+
+ public @NonNull BroadcastRecord getActive() {
+ checkState(isActive(), "isActive");
+ return mActive;
+ }
+
+ public @NonNull Object getActiveReceiver() {
+ checkState(isActive(), "isActive");
+ return mActive.receivers.get(mActiveIndex);
+ }
+
+ public boolean isActive() {
+ return mActive != null;
+ }
+
+ public boolean isRunnable() {
+ if (mRunnableAtInvalidated) updateRunnableAt();
+ return mRunnableAt != Long.MAX_VALUE;
+ }
+
+ /**
+ * Return time at which this process is considered runnable. This is
+ * typically the time at which the next pending broadcast was first
+ * enqueued, but it also reflects any pauses or delays that should be
+ * applied to the process.
+ * <p>
+ * Returns {@link Long#MAX_VALUE} when this queue isn't currently runnable,
+ * typically when the queue is empty or when paused.
+ */
+ public @UptimeMillisLong long getRunnableAt() {
+ if (mRunnableAtInvalidated) updateRunnableAt();
+ return mRunnableAt;
+ }
+
+ private void invalidateRunnableAt() {
+ mRunnableAtInvalidated = true;
+ }
+
+ /**
+ * Update {@link #getRunnableAt()} if it's currently invalidated.
+ */
+ private void updateRunnableAt() {
+ final SomeArgs next = mPending.peekFirst();
+ if (next != null) {
+ final long runnableAt = ((BroadcastRecord) next.arg1).enqueueTime;
+ if (mCountForeground > 0) {
+ mRunnableAt = runnableAt;
+ } else if (mCountOrdered > 0) {
+ mRunnableAt = runnableAt;
+ } else if (mCountAlarm > 0) {
+ mRunnableAt = runnableAt;
+ } else if (mProcessCached) {
+ mRunnableAt = runnableAt + DELAY_CACHED_MILLIS;
+ } else {
+ mRunnableAt = runnableAt + DELAY_DEFAULT_MILLIS;
+ }
+ } else {
+ mRunnableAt = Long.MAX_VALUE;
+ }
+ }
+
+ @Override
+ public int compareTo(BroadcastProcessQueue o) {
+ if (mRunnableAtInvalidated) updateRunnableAt();
+ if (o.mRunnableAtInvalidated) o.updateRunnableAt();
+ return Long.compare(mRunnableAt, o.mRunnableAt);
+ }
+
+ @Override
+ public String toString() {
+ return "BroadcastProcessQueue{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + processName + "/" + UserHandle.formatUid(uid) + "}";
+ }
+
+ public String toShortString() {
+ return processName + "/" + UserHandle.formatUid(uid);
+ }
+
+ public void dumpLocked(@NonNull IndentingPrintWriter pw) {
+ if ((mActive == null) && mPending.isEmpty()) return;
+
+ pw.println(toShortString());
+ pw.increaseIndent();
+ if (mActive != null) {
+ pw.print("🏃 ");
+ pw.print(mActive.toShortString());
+ pw.print(' ');
+ pw.println(mActive.receivers.get(mActiveIndex));
+ }
+ for (SomeArgs args : mPending) {
+ final BroadcastRecord r = (BroadcastRecord) args.arg1;
+ pw.print("\u3000 ");
+ pw.print(r.toShortString());
+ pw.print(' ');
+ pw.println(r.receivers.get(args.argi1));
+ }
+ pw.decreaseIndent();
+ }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 6814509..e6c446a 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
+import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
@@ -59,6 +60,16 @@
mConstants.startObserving(mHandler, resolver);
}
+ static void checkState(boolean state, String msg) {
+ if (!state) {
+ Slog.wtf(TAG, msg, new Throwable());
+ }
+ }
+
+ static void logv(String msg) {
+ Slog.v(TAG, msg);
+ }
+
@Override
public String toString() {
return mQueueName;
@@ -74,6 +85,7 @@
* otherwise {@link ProcessList#SCHED_GROUP_UNDEFINED} if this queue
* has no opinion.
*/
+ @GuardedBy("mService")
public abstract int getPreferredSchedulingGroupLocked(@NonNull ProcessRecord app);
/**
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 8c55b14..169f857 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -1408,7 +1408,7 @@
info.activityInfo.applicationInfo, true,
r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
- r.intent.getAction(), getHostingRecordTriggerType(r)),
+ r.intent.getAction(), r.getHostingRecordTriggerType()),
isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
(r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
if (r.curApp == null) {
@@ -1431,17 +1431,6 @@
mPendingBroadcastRecvIndex = recIdx;
}
- private String getHostingRecordTriggerType(BroadcastRecord r) {
- if (r.alarm) {
- return HostingRecord.TRIGGER_TYPE_ALARM;
- } else if (r.pushMessage) {
- return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE;
- } else if (r.pushMessageOverQuota) {
- return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
- }
- return HostingRecord.TRIGGER_TYPE_UNKNOWN;
- }
-
@Nullable
private String getTargetPackage(BroadcastRecord r) {
if (r.intent == null) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
new file mode 100644
index 0000000..b5e7b86
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -0,0 +1,649 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
+import static com.android.server.am.BroadcastRecord.getReceiverUid;
+import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.IApplicationThread;
+import android.app.RemoteServiceException.CannotDeliverBroadcastException;
+import android.app.UidObserver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Alternative {@link BroadcastQueue} implementation which pivots broadcasts to
+ * be dispatched on a per-process basis.
+ * <p>
+ * Each process now has its own broadcast queue represented by a
+ * {@link BroadcastProcessQueue} instance. Each queue has a concept of being
+ * "runnable at" a particular time in the future, which supports arbitrarily
+ * pausing or delaying delivery on a per-process basis.
+ */
+class BroadcastQueueModernImpl extends BroadcastQueue {
+ BroadcastQueueModernImpl(ActivityManagerService service, Handler handler,
+ BroadcastConstants constants) {
+ this(service, handler, constants, new BroadcastSkipPolicy(service),
+ new BroadcastHistory());
+ }
+
+ BroadcastQueueModernImpl(ActivityManagerService service, Handler handler,
+ BroadcastConstants constants, BroadcastSkipPolicy skipPolicy,
+ BroadcastHistory history) {
+ super(service, handler, "modern", constants, skipPolicy, history);
+ mLocalHandler = new Handler(handler.getLooper(), mLocalCallback);
+ }
+
+ // TODO: add support for ordered broadcasts
+ // TODO: add support for replacing pending broadcasts
+ // TODO: add support for merging pending broadcasts
+
+ // TODO: add trace points for debugging broadcast flows
+ // TODO: record broadcast state change timing statistics
+ // TODO: record historical broadcast statistics
+
+ // TODO: pause queues for apps involved in backup/restore
+ // TODO: pause queues when background services are running
+ // TODO: pause queues when processes are frozen
+
+ // TODO: clean up queues for removed apps
+
+ /**
+ * Maximum number of process queues to dispatch broadcasts to
+ * simultaneously.
+ */
+ // TODO: shift hard-coded defaults to BroadcastConstants
+ private static final int MAX_RUNNING_PROCESS_QUEUES = 4;
+
+ /**
+ * Map from UID to per-process broadcast queues. If a UID hosts more than
+ * one process, each additional process is stored as a linked list using
+ * {@link BroadcastProcessQueue#next}.
+ *
+ * @see #getProcessQueue
+ * @see #getOrCreateProcessQueue
+ */
+ @GuardedBy("mService")
+ private final SparseArray<BroadcastProcessQueue> mProcessQueues = new SparseArray<>();
+
+ /**
+ * Collection of queues which are "runnable". They're sorted by
+ * {@link BroadcastProcessQueue#getRunnableAt()} so that we prefer
+ * dispatching of longer-waiting broadcasts first.
+ */
+ @GuardedBy("mService")
+ private final ArrayList<BroadcastProcessQueue> mRunnable = new ArrayList<>();
+
+ /**
+ * Collection of queues which are "running". This will never be larger than
+ * {@link #MAX_RUNNING_PROCESS_QUEUES}.
+ */
+ @GuardedBy("mService")
+ private final ArrayList<BroadcastProcessQueue> mRunning = new ArrayList<>();
+
+ /**
+ * Single queue which is "running" but is awaiting a cold start to be
+ * completed via {@link #onApplicationAttachedLocked}. To optimize for
+ * system health we only request one cold start at a time.
+ */
+ @GuardedBy("mService")
+ private @Nullable BroadcastProcessQueue mRunningColdStart;
+
+ /**
+ * Collection of latches waiting for queue to go idle.
+ */
+ @GuardedBy("mService")
+ private final ArrayList<CountDownLatch> mWaitingForIdle = new ArrayList<>();
+
+ private static final int MSG_UPDATE_RUNNING_LIST = 1;
+
+ private void enqueueUpdateRunningList() {
+ mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
+ mLocalHandler.sendEmptyMessage(MSG_UPDATE_RUNNING_LIST);
+ }
+
+ private final Handler mLocalHandler;
+
+ private final Handler.Callback mLocalCallback = (msg) -> {
+ switch (msg.what) {
+ case MSG_UPDATE_RUNNING_LIST: {
+ synchronized (mService) {
+ updateRunningList();
+ }
+ return true;
+ }
+ }
+ return false;
+ };
+
+ /**
+ * Consider updating the list of "runnable" queues, specifically with
+ * relation to the given queue.
+ * <p>
+ * Typically called when {@link BroadcastProcessQueue#getRunnableAt()} might
+ * have changed, since that influences the order in which we'll promote a
+ * "runnable" queue to be "running."
+ */
+ @GuardedBy("mService")
+ private void updateRunnableList(@NonNull BroadcastProcessQueue queue) {
+ if (mRunning.contains(queue)) {
+ // Already running; they'll be reinserted into the runnable list
+ // once they finish running, so no need to update them now
+ return;
+ }
+
+ // TODO: better optimize by using insertion sort data structure
+ mRunnable.remove(queue);
+ if (queue.isRunnable()) {
+ mRunnable.add(queue);
+ }
+ mRunnable.sort(null);
+ }
+
+ /**
+ * Consider updating the list of "running" queues.
+ * <p>
+ * This method can promote "runnable" queues to become "running", subject to
+ * a maximum of {@link #MAX_RUNNING_PROCESS_QUEUES} warm processes and only
+ * one pending cold-start.
+ */
+ @GuardedBy("mService")
+ private void updateRunningList() {
+ int avail = MAX_RUNNING_PROCESS_QUEUES - mRunning.size();
+ if (avail == 0) return;
+
+ // If someone is waiting to go idle, everything is runnable now
+ final boolean waitingForIdle = !mWaitingForIdle.isEmpty();
+
+ // We're doing an update now, so remove any future update requests;
+ // we'll repost below if needed
+ mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
+
+ boolean updateOomAdj = false;
+ final long now = SystemClock.uptimeMillis();
+ for (int i = 0; i < mRunnable.size() && avail > 0; i++) {
+ final BroadcastProcessQueue queue = mRunnable.get(i);
+ final long runnableAt = queue.getRunnableAt();
+
+ // If queues beyond this point aren't ready to run yet, schedule
+ // another pass when they'll be runnable
+ if (runnableAt > now && !waitingForIdle) {
+ mLocalHandler.sendEmptyMessageAtTime(MSG_UPDATE_RUNNING_LIST, runnableAt);
+ break;
+ }
+
+ // We might not have heard about a newly running process yet, so
+ // consider refreshing if we think we're cold
+ updateWarmProcess(queue);
+
+ final boolean processWarm = queue.isProcessWarm();
+ if (!processWarm) {
+ // We only offer to run one cold-start at a time to preserve
+ // system resources; below we either claim that single slot or
+ // skip to look for another warm process
+ if (mRunningColdStart == null) {
+ mRunningColdStart = queue;
+ } else {
+ continue;
+ }
+ }
+
+ if (DEBUG_BROADCAST) logv("Promoting " + queue
+ + " from runnable to running; process is " + queue.app);
+
+ // Allocate this available permit and start running!
+ mRunnable.remove(i);
+ mRunning.add(queue);
+ avail--;
+ i--;
+
+ queue.makeActiveNextPending();
+
+ // If we're already warm, schedule it; otherwise we'll wait for the
+ // cold start to circle back around
+ if (processWarm) {
+ scheduleReceiverWarmLocked(queue);
+ } else {
+ scheduleReceiverColdLocked(queue);
+ }
+
+ mService.enqueueOomAdjTargetLocked(queue.app);
+ updateOomAdj = true;
+ }
+
+ if (updateOomAdj) {
+ mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
+ }
+
+ if (waitingForIdle && isIdleLocked()) {
+ mWaitingForIdle.forEach((latch) -> latch.countDown());
+ mWaitingForIdle.clear();
+ }
+ }
+
+ @Override
+ public boolean onApplicationAttachedLocked(@NonNull ProcessRecord app) {
+ boolean didSomething = false;
+ if ((mRunningColdStart != null) && (mRunningColdStart.app == app)) {
+ // We've been waiting for this app to cold start, and it's ready
+ // now; dispatch its next broadcast and clear the slot
+ scheduleReceiverWarmLocked(mRunningColdStart);
+ mRunningColdStart = null;
+
+ // We might be willing to kick off another cold start
+ enqueueUpdateRunningList();
+ didSomething = true;
+ }
+ return didSomething;
+ }
+
+ @Override
+ public boolean onApplicationTimeoutLocked(@NonNull ProcessRecord app) {
+ return onApplicationCleanupLocked(app);
+ }
+
+ @Override
+ public boolean onApplicationProblemLocked(@NonNull ProcessRecord app) {
+ return onApplicationCleanupLocked(app);
+ }
+
+ @Override
+ public boolean onApplicationCleanupLocked(@NonNull ProcessRecord app) {
+ boolean didSomething = false;
+ if ((mRunningColdStart != null) && (mRunningColdStart.app == app)) {
+ // We've been waiting for this app to cold start, and it had
+ // trouble; clear the slot and fail delivery below
+ mRunningColdStart = null;
+
+ // We might be willing to kick off another cold start
+ enqueueUpdateRunningList();
+ didSomething = true;
+ }
+
+ final BroadcastProcessQueue queue = getProcessQueue(app);
+ if (queue != null) {
+ queue.app = null;
+
+ // If queue was running a broadcast, fail it
+ if (queue.isActive()) {
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+ didSomething = true;
+ }
+ }
+
+ return didSomething;
+ }
+
+ @Override
+ public int getPreferredSchedulingGroupLocked(@NonNull ProcessRecord app) {
+ final BroadcastProcessQueue queue = getProcessQueue(app);
+ if ((queue != null) && mRunning.contains(queue)) {
+ return queue.getPreferredSchedulingGroupLocked();
+ }
+ return ProcessList.SCHED_GROUP_UNDEFINED;
+ }
+
+ @Override
+ public void enqueueBroadcastLocked(@NonNull BroadcastRecord r) {
+ // TODO: handle empty receivers to deliver result immediately
+ if (r.receivers == null) return;
+
+ r.enqueueTime = SystemClock.uptimeMillis();
+ r.enqueueRealTime = SystemClock.elapsedRealtime();
+ r.enqueueClockTime = System.currentTimeMillis();
+
+ for (int i = 0; i < r.receivers.size(); i++) {
+ final Object receiver = r.receivers.get(i);
+ final BroadcastProcessQueue queue = getOrCreateProcessQueue(
+ getReceiverProcessName(receiver), getReceiverUid(receiver));
+ queue.enqueueBroadcast(r, i);
+ updateRunnableList(queue);
+ enqueueUpdateRunningList();
+ }
+ }
+
+ private void scheduleReceiverColdLocked(@NonNull BroadcastProcessQueue queue) {
+ checkState(queue.isActive(), "isActive");
+
+ final BroadcastRecord r = queue.getActive();
+ final Object receiver = queue.getActiveReceiver();
+
+ final ApplicationInfo info = ((ResolveInfo) receiver).activityInfo.applicationInfo;
+ final ComponentName component = ((ResolveInfo) receiver).activityInfo.getComponentName();
+
+ final int intentFlags = r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND;
+ final HostingRecord hostingRecord = new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST,
+ component, r.intent.getAction(), r.getHostingRecordTriggerType());
+ final boolean isActivityCapable = (r.options != null
+ && r.options.getTemporaryAppAllowlistDuration() > 0);
+ final int zygotePolicyFlags = isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE
+ : ZYGOTE_POLICY_FLAG_EMPTY;
+ final boolean allowWhileBooting = (r.intent.getFlags()
+ & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0;
+
+ if (DEBUG_BROADCAST) logv("Scheduling " + r + " to cold " + queue);
+ queue.app = mService.startProcessLocked(queue.processName, info, true, intentFlags,
+ hostingRecord, zygotePolicyFlags, allowWhileBooting, false);
+ if (queue.app == null) {
+ mRunningColdStart = null;
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+ }
+ }
+
+ private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) {
+ checkState(queue.isActive(), "isActive");
+
+ final ProcessRecord app = queue.app;
+ final BroadcastRecord r = queue.getActive();
+ final Object receiver = queue.getActiveReceiver();
+
+ // TODO: schedule ANR timeout trigger event
+ // TODO: apply temp allowlist exemptions
+ // TODO: apply background activity launch exemptions
+
+ if (mSkipPolicy.shouldSkip(r, receiver)) {
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+ return;
+ }
+
+ final Intent receiverIntent = r.getReceiverIntent(receiver);
+ if (receiverIntent == null) {
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+ return;
+ }
+
+ if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
+ final IApplicationThread thread = app.getThread();
+ if (thread != null) {
+ try {
+ queue.setActiveDeliveryState(BroadcastRecord.DELIVERY_SCHEDULED);
+ if (receiver instanceof BroadcastFilter) {
+ thread.scheduleRegisteredReceiver(
+ ((BroadcastFilter) receiver).receiverList.receiver, receiverIntent,
+ r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky,
+ r.userId, app.mState.getReportedProcState());
+
+ // TODO: consider making registered receivers of unordered
+ // broadcasts report results to detect ANRs
+ if (!r.ordered) {
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
+ }
+ } else {
+ thread.scheduleReceiver(receiverIntent, ((ResolveInfo) receiver).activityInfo,
+ null, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
+ app.mState.getReportedProcState());
+ }
+ } catch (RemoteException e) {
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+ synchronized (app.mService) {
+ app.scheduleCrashLocked(TAG, CannotDeliverBroadcastException.TYPE_ID, null);
+ }
+ }
+ } else {
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+ }
+ }
+
+ @Override
+ public boolean finishReceiverLocked(@NonNull ProcessRecord app, int resultCode,
+ @Nullable String resultData, @Nullable Bundle resultExtras, boolean resultAbort,
+ boolean waitForServices) {
+ final BroadcastProcessQueue queue = getProcessQueue(app);
+ return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
+ }
+
+ private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue, int deliveryState) {
+ checkState(queue.isActive(), "isActive");
+
+ if (deliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
+ Slog.w(TAG, "Failed delivery of " + queue.getActive() + " to " + queue);
+ }
+
+ queue.setActiveDeliveryState(deliveryState);
+
+ // TODO: cancel any outstanding ANR timeout
+ // TODO: limit number of broadcasts in a row to avoid starvation
+ // TODO: if we're the last receiver of this broadcast, record to history
+
+ if (queue.isRunnable() && queue.isProcessWarm()) {
+ // We're on a roll; move onto the next broadcast for this process
+ queue.makeActiveNextPending();
+ scheduleReceiverWarmLocked(queue);
+ return true;
+ } else {
+ // We've drained running broadcasts; maybe move back to runnable
+ queue.makeActiveIdle();
+ mRunning.remove(queue);
+ // App is no longer running a broadcast, so update its OOM
+ // adjust during our next pass; no need for an immediate update
+ mService.enqueueOomAdjTargetLocked(queue.app);
+ updateRunnableList(queue);
+ enqueueUpdateRunningList();
+ return false;
+ }
+ }
+
+ @Override
+ public boolean cleanupDisabledPackageReceiversLocked(String packageName,
+ Set<String> filterByClasses, int userId, boolean doit) {
+ // TODO: implement
+ return false;
+ }
+
+ @Override
+ void start(@NonNull ContentResolver resolver) {
+ super.start(resolver);
+
+ mService.registerUidObserver(new UidObserver() {
+ @Override
+ public void onUidCachedChanged(int uid, boolean cached) {
+ synchronized (mService) {
+ BroadcastProcessQueue leaf = mProcessQueues.get(uid);
+ while (leaf != null) {
+ leaf.setProcessCached(cached);
+ updateRunnableList(leaf);
+ leaf = leaf.next;
+ }
+ enqueueUpdateRunningList();
+ }
+ }
+ }, ActivityManager.UID_OBSERVER_CACHED, 0, "android");
+ }
+
+ @Override
+ public boolean isIdleLocked() {
+ return mRunnable.isEmpty() && mRunning.isEmpty();
+ }
+
+ @Override
+ public void waitForIdle(@Nullable PrintWriter pw) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ synchronized (mService) {
+ mWaitingForIdle.add(latch);
+ }
+ enqueueUpdateRunningList();
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void waitForBarrier(@Nullable PrintWriter pw) {
+ // TODO: implement
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String describeStateLocked() {
+ return mRunnable.size() + " runnable, " + mRunning.size() + " running";
+ }
+
+ @Override
+ public boolean isDelayBehindServices() {
+ // TODO: implement
+ return false;
+ }
+
+ @Override
+ public void backgroundServicesFinishedLocked(int userId) {
+ // TODO: implement
+ }
+
+ private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) {
+ if (!queue.isProcessWarm()) {
+ queue.app = mService.getProcessRecordLocked(queue.processName, queue.uid);
+ }
+ }
+
+ private @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull ProcessRecord app) {
+ return getOrCreateProcessQueue(app.processName, app.info.uid);
+ }
+
+ private @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull String processName,
+ int uid) {
+ BroadcastProcessQueue leaf = mProcessQueues.get(uid);
+ while (leaf != null) {
+ if (Objects.equals(leaf.processName, processName)) {
+ return leaf;
+ } else if (leaf.next == null) {
+ break;
+ }
+ leaf = leaf.next;
+ }
+
+ BroadcastProcessQueue created = new BroadcastProcessQueue(processName, uid);
+ created.app = mService.getProcessRecordLocked(processName, uid);
+
+ if (leaf == null) {
+ mProcessQueues.put(uid, created);
+ } else {
+ leaf.next = created;
+ }
+ return created;
+ }
+
+ private @Nullable BroadcastProcessQueue getProcessQueue(@NonNull ProcessRecord app) {
+ return getProcessQueue(app.processName, app.info.uid);
+ }
+
+ private @Nullable BroadcastProcessQueue getProcessQueue(@NonNull String processName, int uid) {
+ BroadcastProcessQueue leaf = mProcessQueues.get(uid);
+ while (leaf != null) {
+ if (Objects.equals(leaf.processName, processName)) {
+ return leaf;
+ }
+ leaf = leaf.next;
+ }
+ return null;
+ }
+
+ @Override
+ public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
+ mHistory.dumpDebug(proto);
+ proto.end(token);
+ }
+
+ @Override
+ public boolean dumpLocked(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
+ @NonNull String[] args, int opti, boolean dumpAll, @Nullable String dumpPackage,
+ boolean needSep) {
+ final long now = SystemClock.uptimeMillis();
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ ipw.increaseIndent();
+
+ ipw.println();
+ ipw.println("📋 Per-process queues:");
+ ipw.increaseIndent();
+ for (int i = 0; i < mProcessQueues.size(); i++) {
+ BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
+ while (leaf != null) {
+ leaf.dumpLocked(ipw);
+ leaf = leaf.next;
+ }
+ }
+ ipw.decreaseIndent();
+
+ ipw.println();
+ ipw.println("🧍 Runnable:");
+ ipw.increaseIndent();
+ if (mRunnable.isEmpty()) {
+ ipw.println("(none)");
+ } else {
+ for (BroadcastProcessQueue queue : mRunnable) {
+ TimeUtils.formatDuration(queue.getRunnableAt(), now, ipw);
+ ipw.print(' ');
+ ipw.println(queue.toShortString());
+ }
+ }
+ ipw.decreaseIndent();
+
+ ipw.println();
+ ipw.println("🏃 Running:");
+ ipw.increaseIndent();
+ if (mRunning.isEmpty()) {
+ ipw.println("(none)");
+ } else {
+ for (BroadcastProcessQueue queue : mRunning) {
+ if (queue == mRunningColdStart) {
+ ipw.print("🥶 ");
+ } else {
+ ipw.print("\u3000 ");
+ }
+ ipw.println(queue.toShortString());
+ }
+ }
+ ipw.decreaseIndent();
+
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+ needSep = mHistory.dumpLocked(ipw, dumpPackage, mQueueName, sdf, dumpAll, needSep);
+ return needSep;
+ }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 817831c..96fd362 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -84,6 +84,7 @@
final BroadcastOptions options; // BroadcastOptions supplied by caller
final List receivers; // contains BroadcastFilter and ResolveInfo
final int[] delivery; // delivery state of each receiver
+ final long[] scheduledTime; // uptimeMillis when each receiver was scheduled
final long[] duration; // duration a receiver took to process broadcast
IIntentReceiver resultTo; // who receives final result if non-null
boolean deferred;
@@ -127,10 +128,18 @@
static final int CALL_DONE_RECEIVE = 3;
static final int WAITING_SERVICES = 4;
+ /** Initial state: waiting to run in future */
static final int DELIVERY_PENDING = 0;
+ /** Terminal state: finished successfully */
static final int DELIVERY_DELIVERED = 1;
+ /** Terminal state: skipped due to internal policy */
static final int DELIVERY_SKIPPED = 2;
+ /** Terminal state: timed out during attempted delivery */
static final int DELIVERY_TIMEOUT = 3;
+ /** Intermediate state: currently executing */
+ static final int DELIVERY_SCHEDULED = 4;
+ /** Terminal state: failure to dispatch */
+ static final int DELIVERY_FAILURE = 5;
ProcessRecord curApp; // hosting application of current receiver.
ComponentName curComponent; // the receiver class that is currently running.
@@ -253,6 +262,8 @@
case DELIVERY_DELIVERED: pw.print("Deliver"); break;
case DELIVERY_SKIPPED: pw.print("Skipped"); break;
case DELIVERY_TIMEOUT: pw.print("Timeout"); break;
+ case DELIVERY_SCHEDULED: pw.print("Schedul"); break;
+ case DELIVERY_FAILURE: pw.print("Failure"); break;
default: pw.print("???????"); break;
}
pw.print(" "); TimeUtils.formatDuration(duration[i], pw);
@@ -300,6 +311,7 @@
options = _options;
receivers = _receivers;
delivery = new int[_receivers != null ? _receivers.size() : 0];
+ scheduledTime = new long[delivery.length];
duration = new long[delivery.length];
resultTo = _resultTo;
resultCode = _resultCode;
@@ -346,6 +358,7 @@
options = from.options;
receivers = from.receivers;
delivery = from.delivery;
+ scheduledTime = from.scheduledTime;
duration = from.duration;
resultTo = from.resultTo;
enqueueTime = from.enqueueTime;
@@ -497,7 +510,77 @@
return ret;
}
- int getReceiverUid(Object receiver) {
+ /**
+ * Update the delivery state of the given {@link #receivers} index.
+ * Automatically updates any time measurements related to state changes.
+ */
+ void setDeliveryState(int index, int deliveryState) {
+ delivery[index] = deliveryState;
+
+ switch (deliveryState) {
+ case DELIVERY_DELIVERED:
+ duration[index] = SystemClock.uptimeMillis() - scheduledTime[index];
+ break;
+ case DELIVERY_SCHEDULED:
+ scheduledTime[index] = SystemClock.uptimeMillis();
+ break;
+ }
+ }
+
+ boolean isForeground() {
+ return (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
+ }
+
+ boolean isReplacePending() {
+ return (intent.getFlags() & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
+ }
+
+ @NonNull String getHostingRecordTriggerType() {
+ if (alarm) {
+ return HostingRecord.TRIGGER_TYPE_ALARM;
+ } else if (pushMessage) {
+ return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE;
+ } else if (pushMessageOverQuota) {
+ return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
+ }
+ return HostingRecord.TRIGGER_TYPE_UNKNOWN;
+ }
+
+ /**
+ * Return an instance of {@link #intent} specialized for the given receiver.
+ * For example, this returns a new specialized instance if the extras need
+ * to be filtered, or a {@link ResolveInfo} needs to be configured.
+ *
+ * @return a specialized intent, otherwise {@code null} to indicate that the
+ * broadcast should not be delivered to this receiver, typically due
+ * to it being filtered away by {@link #filterExtrasForReceiver}.
+ */
+ @Nullable Intent getReceiverIntent(@NonNull Object receiver) {
+ Intent newIntent = null;
+ if (filterExtrasForReceiver != null) {
+ final Bundle extras = intent.getExtras();
+ if (extras != null) {
+ final int receiverUid = getReceiverUid(receiver);
+ final Bundle filteredExtras = filterExtrasForReceiver.apply(receiverUid, extras);
+ if (filteredExtras == null) {
+ // Completely filtered; skip the broadcast!
+ return null;
+ } else {
+ newIntent = new Intent(intent);
+ newIntent.replaceExtras(filteredExtras);
+ }
+ }
+ }
+ if (receiver instanceof ResolveInfo) {
+ if (newIntent == null) {
+ newIntent = new Intent(intent);
+ }
+ newIntent.setComponent(((ResolveInfo) receiver).activityInfo.getComponentName());
+ }
+ return (newIntent != null) ? newIntent : intent;
+ }
+
+ static int getReceiverUid(@NonNull Object receiver) {
if (receiver instanceof BroadcastFilter) {
return ((BroadcastFilter) receiver).owningUid;
} else /* if (receiver instanceof ResolveInfo) */ {
@@ -505,6 +588,14 @@
}
}
+ static String getReceiverProcessName(@NonNull Object receiver) {
+ if (receiver instanceof BroadcastFilter) {
+ return ((BroadcastFilter) receiver).receiverList.app.processName;
+ } else /* if (receiver instanceof ResolveInfo) */ {
+ return ((ResolveInfo) receiver).activityInfo.processName;
+ }
+ }
+
public BroadcastRecord maybeStripForHistory() {
if (!intent.canStripForHistory()) {
return this;
@@ -561,6 +652,10 @@
+ " u" + userId + " " + intent.getAction() + "}";
}
+ public String toShortString() {
+ return intent.getAction() + "/u" + userId;
+ }
+
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
proto.write(BroadcastRecordProto.USER_ID, userId);
diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
index 9569848..e9b5030 100644
--- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
+++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
@@ -16,7 +16,6 @@
package com.android.server.am;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
import static com.android.server.am.ActivityManagerService.checkComponentPermission;
import static com.android.server.am.BroadcastQueue.TAG;
@@ -35,7 +34,6 @@
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
-import android.os.Bundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -59,6 +57,18 @@
/**
* Determine if the given {@link BroadcastRecord} is eligible to be sent to
+ * the given {@link BroadcastFilter} or {@link ResolveInfo}.
+ */
+ public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull Object target) {
+ if (target instanceof BroadcastFilter) {
+ return shouldSkip(r, (BroadcastFilter) target);
+ } else {
+ return shouldSkip(r, (ResolveInfo) target);
+ }
+ }
+
+ /**
+ * Determine if the given {@link BroadcastRecord} is eligible to be sent to
* the given {@link ResolveInfo}.
*/
public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull ResolveInfo info) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 6775c99..7c7b01c9 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1641,7 +1641,7 @@
}
mInjector.updateUserConfiguration();
updateCurrentProfileIds();
- mInjector.getWindowManager().setCurrentUser(userId, getCurrentProfileIds());
+ mInjector.getWindowManager().setCurrentUser(userId);
mInjector.reportCurWakefulnessUsageEvent();
// Once the internal notion of the active user has switched, we lock the device
// with the option to show the user switcher on the keyguard.
@@ -1655,7 +1655,6 @@
} else {
final Integer currentUserIdInt = mCurrentUserId;
updateCurrentProfileIds();
- mInjector.getWindowManager().setCurrentProfileIds(getCurrentProfileIds());
synchronized (mLock) {
mUserLru.remove(currentUserIdInt);
mUserLru.add(currentUserIdInt);
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 676dc19..6a53978 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3210,41 +3210,44 @@
return;
}
- if (mSession != null && mMobikeEnabled) {
- Log.d(
- TAG,
- "IKE Session has mobility. Delay handleSessionLost for losing network "
- + network
- + " on session with token "
- + mCurrentToken);
+ Log.d(TAG, "Schedule a delay handleSessionLost for losing network "
+ + network
+ + " on session with token "
+ + mCurrentToken);
- final int token = mCurrentToken;
- // Delay the teardown in case a new network will be available soon. For example,
- // during handover between two WiFi networks, Android will disconnect from the
- // first WiFi and then connects to the second WiFi.
- mScheduledHandleNetworkLostFuture =
- mExecutor.schedule(
- () -> {
- if (isActiveToken(token)) {
- handleSessionLost(null /* exception */, network);
- } else {
- Log.d(
- TAG,
- "Scheduled handleSessionLost fired for "
- + "obsolete token "
- + token);
+ final int token = mCurrentToken;
+ // Delay the teardown in case a new network will be available soon. For example,
+ // during handover between two WiFi networks, Android will disconnect from the
+ // first WiFi and then connects to the second WiFi.
+ mScheduledHandleNetworkLostFuture =
+ mExecutor.schedule(
+ () -> {
+ if (isActiveToken(token)) {
+ handleSessionLost(new IkeNetworkLostException(network),
+ network);
+
+ synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
+ updateState(DetailedState.DISCONNECTED,
+ "Network lost");
}
+ } else {
+ Log.d(
+ TAG,
+ "Scheduled handleSessionLost fired for "
+ + "obsolete token "
+ + token);
+ }
- // Reset mScheduledHandleNetworkLostFuture since it's
- // already run on executor thread.
- mScheduledHandleNetworkLostFuture = null;
- },
- NETWORK_LOST_TIMEOUT_MS,
- TimeUnit.MILLISECONDS);
- } else {
- Log.d(TAG, "Call handleSessionLost for losing network " + network);
- handleSessionLost(null /* exception */, network);
- }
+ // Reset mScheduledHandleNetworkLostFuture since it's
+ // already run on executor thread.
+ mScheduledHandleNetworkLostFuture = null;
+ },
+ NETWORK_LOST_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
+
}
private void cancelHandleNetworkLostTimeout() {
@@ -4185,8 +4188,6 @@
*/
@NonNull
public synchronized List<String> getAppExclusionList(@NonNull String packageName) {
- enforceNotRestrictedUser();
-
final long oldId = Binder.clearCallingIdentity();
try {
final byte[] bytes = getVpnProfileStore().get(getVpnAppExcludedForPackage(packageName));
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 8e00ccf..44c8e18 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -21,8 +21,11 @@
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
+import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE;
+import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE;
import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
+import static com.android.server.devicestate.OverrideRequestController.STATUS_UNKNOWN;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -106,6 +109,8 @@
private final BinderService mBinderService;
@NonNull
private final OverrideRequestController mOverrideRequestController;
+ @NonNull
+ private final DeviceStateProviderListener mDeviceStateProviderListener;
@VisibleForTesting
@NonNull
public ActivityTaskManagerInternal mActivityTaskManagerInternal;
@@ -139,6 +144,12 @@
@NonNull
private Optional<OverrideRequest> mActiveOverride = Optional.empty();
+ // The current active base state override request. When set the device state specified here will
+ // replace the value in mBaseState.
+ @GuardedBy("mLock")
+ @NonNull
+ private Optional<OverrideRequest> mActiveBaseStateOverride = Optional.empty();
+
// List of processes registered to receive notifications about changes to device state and
// request status indexed by process id.
@GuardedBy("mLock")
@@ -177,7 +188,8 @@
mOverrideRequestController = new OverrideRequestController(
this::onOverrideRequestStatusChangedLocked);
mDeviceStatePolicy = policy;
- mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener());
+ mDeviceStateProviderListener = new DeviceStateProviderListener();
+ mDeviceStatePolicy.getDeviceStateProvider().setListener(mDeviceStateProviderListener);
mBinderService = new BinderService();
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
}
@@ -257,6 +269,21 @@
}
}
+ /**
+ * Returns the current override base state, or {@link Optional#empty()} if no override state is
+ * requested. If an override base state is present, the returned state will be the same as
+ * the base state returned from {@link #getBaseState()}.
+ */
+ @NonNull
+ Optional<DeviceState> getOverrideBaseState() {
+ synchronized (mLock) {
+ if (mActiveBaseStateOverride.isPresent()) {
+ return getStateLocked(mActiveBaseStateOverride.get().getRequestedState());
+ }
+ return Optional.empty();
+ }
+ }
+
/** Returns the list of currently supported device states. */
DeviceState[] getSupportedStates() {
synchronized (mLock) {
@@ -366,6 +393,7 @@
}
final DeviceState baseState = baseStateOptional.get();
+
if (mBaseState.isPresent() && mBaseState.get().equals(baseState)) {
// Base state hasn't changed. Nothing to do.
return;
@@ -375,7 +403,7 @@
if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
mOverrideRequestController.cancelOverrideRequest();
}
- mOverrideRequestController.handleBaseStateChanged();
+ mOverrideRequestController.handleBaseStateChanged(identifier);
updatePendingStateLocked();
if (!mPendingState.isPresent()) {
@@ -528,16 +556,41 @@
}
}
+ @GuardedBy("mLock")
private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request,
@OverrideRequestController.RequestStatus int status) {
- if (status == STATUS_ACTIVE) {
- mActiveOverride = Optional.of(request);
- } else if (status == STATUS_CANCELED) {
- if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
- mActiveOverride = Optional.empty();
+ if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_BASE_STATE) {
+ switch (status) {
+ case STATUS_ACTIVE:
+ enableBaseStateRequestLocked(request);
+ return;
+ case STATUS_CANCELED:
+ if (mActiveBaseStateOverride.isPresent()
+ && mActiveBaseStateOverride.get() == request) {
+ mActiveBaseStateOverride = Optional.empty();
+ }
+ break;
+ case STATUS_UNKNOWN: // same as default
+ default:
+ throw new IllegalArgumentException("Unknown request status: " + status);
+ }
+ } else if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_EMULATED_STATE) {
+ switch (status) {
+ case STATUS_ACTIVE:
+ mActiveOverride = Optional.of(request);
+ break;
+ case STATUS_CANCELED:
+ if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
+ mActiveOverride = Optional.empty();
+ }
+ break;
+ case STATUS_UNKNOWN: // same as default
+ default:
+ throw new IllegalArgumentException("Unknown request status: " + status);
}
} else {
- throw new IllegalArgumentException("Unknown request status: " + status);
+ throw new IllegalArgumentException(
+ "Unknown OverrideRest type: " + request.getRequestType());
}
boolean updatedPendingState = updatePendingStateLocked();
@@ -564,6 +617,18 @@
mHandler.post(this::notifyPolicyIfNeeded);
}
+ /**
+ * Sets the new base state of the device and notifies the process that made the base state
+ * override request that the request is now active.
+ */
+ @GuardedBy("mLock")
+ private void enableBaseStateRequestLocked(OverrideRequest request) {
+ setBaseState(request.getRequestedState());
+ mActiveBaseStateOverride = Optional.of(request);
+ ProcessRecord processRecord = mProcessRecords.get(request.getPid());
+ processRecord.notifyRequestActiveAsync(request.getToken());
+ }
+
private void registerProcess(int pid, IDeviceStateManagerCallback callback) {
synchronized (mLock) {
if (mProcessRecords.contains(pid)) {
@@ -606,7 +671,8 @@
+ " has no registered callback.");
}
- if (mOverrideRequestController.hasRequest(token)) {
+ if (mOverrideRequestController.hasRequest(token,
+ OVERRIDE_REQUEST_TYPE_EMULATED_STATE)) {
throw new IllegalStateException("Request has already been made for the supplied"
+ " token: " + token);
}
@@ -617,7 +683,8 @@
+ " is not supported.");
}
- OverrideRequest request = new OverrideRequest(token, callingPid, state, flags);
+ OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
+ OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
mOverrideRequestController.addRequest(request);
}
}
@@ -633,6 +700,44 @@
}
}
+ private void requestBaseStateOverrideInternal(int state, int flags, int callingPid,
+ @NonNull IBinder token) {
+ synchronized (mLock) {
+ final Optional<DeviceState> deviceState = getStateLocked(state);
+ if (!deviceState.isPresent()) {
+ throw new IllegalArgumentException("Requested state: " + state
+ + " is not supported.");
+ }
+
+ final ProcessRecord processRecord = mProcessRecords.get(callingPid);
+ if (processRecord == null) {
+ throw new IllegalStateException("Process " + callingPid
+ + " has no registered callback.");
+ }
+
+ if (mOverrideRequestController.hasRequest(token,
+ OVERRIDE_REQUEST_TYPE_BASE_STATE)) {
+ throw new IllegalStateException("Request has already been made for the supplied"
+ + " token: " + token);
+ }
+
+ OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
+ OVERRIDE_REQUEST_TYPE_BASE_STATE);
+ mOverrideRequestController.addBaseStateRequest(request);
+ }
+ }
+
+ private void cancelBaseStateOverrideInternal(int callingPid) {
+ synchronized (mLock) {
+ final ProcessRecord processRecord = mProcessRecords.get(callingPid);
+ if (processRecord == null) {
+ throw new IllegalStateException("Process " + callingPid
+ + " has no registered callback.");
+ }
+ setBaseState(mDeviceStateProviderListener.mCurrentBaseState);
+ }
+ }
+
private void dumpInternal(PrintWriter pw) {
pw.println("DEVICE STATE MANAGER (dumpsys device_state)");
@@ -729,6 +834,8 @@
}
private final class DeviceStateProviderListener implements DeviceStateProvider.Listener {
+ @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int mCurrentBaseState;
+
@Override
public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) {
if (newDeviceStates.length == 0) {
@@ -743,7 +850,7 @@
if (identifier < MINIMUM_DEVICE_STATE || identifier > MAXIMUM_DEVICE_STATE) {
throw new IllegalArgumentException("Invalid identifier: " + identifier);
}
-
+ mCurrentBaseState = identifier;
setBaseState(identifier);
}
}
@@ -895,6 +1002,38 @@
}
@Override // Binder call
+ public void requestBaseStateOverride(IBinder token, int state, int flags) {
+ final int callingPid = Binder.getCallingPid();
+ getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+ "Permission required to control base state of device.");
+
+ if (token == null) {
+ throw new IllegalArgumentException("Request token must not be null.");
+ }
+
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ requestBaseStateOverrideInternal(state, flags, callingPid, token);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @Override // Binder call
+ public void cancelBaseStateOverride() {
+ final int callingPid = Binder.getCallingPid();
+ getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+ "Permission required to control base state of device.");
+
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ cancelBaseStateOverrideInternal(callingPid);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @Override // Binder call
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver result) {
new DeviceStateManagerShellCommand(DeviceStateManagerService.this)
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
index 659ee75..8c6068d 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
@@ -16,11 +16,8 @@
package com.android.server.devicestate;
-import static android.Manifest.permission.CONTROL_DEVICE_STATE;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateRequest;
import android.os.Binder;
@@ -39,6 +36,8 @@
public class DeviceStateManagerShellCommand extends ShellCommand {
@Nullable
private static DeviceStateRequest sLastRequest;
+ @Nullable
+ private static DeviceStateRequest sLastBaseStateRequest;
private final DeviceStateManagerService mService;
private final DeviceStateManager mClient;
@@ -58,6 +57,8 @@
switch (cmd) {
case "state":
return runState(pw);
+ case "base-state":
+ return runBaseState(pw);
case "print-state":
return runPrintState(pw);
case "print-states":
@@ -89,10 +90,6 @@
return 0;
}
- final Context context = mService.getContext();
- context.enforceCallingOrSelfPermission(
- CONTROL_DEVICE_STATE,
- "Permission required to request device state.");
final long callingIdentity = Binder.clearCallingIdentity();
try {
if ("reset".equals(nextArg)) {
@@ -127,6 +124,47 @@
return 0;
}
+ private int runBaseState(PrintWriter pw) {
+ final String nextArg = getNextArg();
+ if (nextArg == null) {
+ printAllStates(pw);
+ return 0;
+ }
+
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ if ("reset".equals(nextArg)) {
+ if (sLastBaseStateRequest != null) {
+ mClient.cancelBaseStateOverride();
+ sLastBaseStateRequest = null;
+ }
+ } else {
+ int requestedState = Integer.parseInt(nextArg);
+ DeviceStateRequest request = DeviceStateRequest.newBuilder(requestedState).build();
+
+ mClient.requestBaseStateOverride(request, null, null);
+
+ sLastBaseStateRequest = request;
+ }
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: requested state should be an integer");
+ return -1;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println("Error: " + e.getMessage());
+ getErrPrintWriter().println("-------------------");
+ getErrPrintWriter().println("Run:");
+ getErrPrintWriter().println("");
+ getErrPrintWriter().println(" print-states");
+ getErrPrintWriter().println("");
+ getErrPrintWriter().println("to get the list of currently supported device states");
+ return -1;
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+
+ return 0;
+ }
+
private int runPrintState(PrintWriter pw) {
Optional<DeviceState> deviceState = mService.getCommittedState();
if (deviceState.isPresent()) {
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java
index 35a4c84..325e384 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequest.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java
@@ -16,9 +16,13 @@
package com.android.server.devicestate;
+import android.annotation.IntDef;
import android.hardware.devicestate.DeviceStateRequest;
import android.os.IBinder;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A request to override the state managed by {@link DeviceStateManagerService}.
*
@@ -30,13 +34,47 @@
private final int mRequestedState;
@DeviceStateRequest.RequestFlags
private final int mFlags;
+ @OverrideRequestType
+ private final int mRequestType;
+
+ /**
+ * Denotes that the request is meant to override the emulated state of the device. This will
+ * not change the base (physical) state of the device.
+ *
+ * This request type should be used if you are looking to emulate a device state for a feature
+ * but want the system to be aware of the physical state of the device.
+ */
+ public static final int OVERRIDE_REQUEST_TYPE_EMULATED_STATE = 0;
+
+ /**
+ * Denotes that the request is meant to override the base (physical) state of the device.
+ * Overriding the base state may not change the emulated state of the device if there is also an
+ * override request active for that property.
+ *
+ * This request type should only be used for testing, where you want to simulate the physical
+ * state of the device changing.
+ */
+ public static final int OVERRIDE_REQUEST_TYPE_BASE_STATE = 1;
+
+ /**
+ * Flags for signifying the type of {@link OverrideRequest}.
+ *
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "REQUEST_TYPE_" }, value = {
+ OVERRIDE_REQUEST_TYPE_BASE_STATE,
+ OVERRIDE_REQUEST_TYPE_EMULATED_STATE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface OverrideRequestType {}
OverrideRequest(IBinder token, int pid, int requestedState,
- @DeviceStateRequest.RequestFlags int flags) {
+ @DeviceStateRequest.RequestFlags int flags, @OverrideRequestType int requestType) {
mToken = token;
mPid = pid;
mRequestedState = requestedState;
mFlags = flags;
+ mRequestType = requestType;
}
IBinder getToken() {
@@ -55,4 +93,9 @@
int getFlags() {
return mFlags;
}
+
+ @OverrideRequestType
+ int getRequestType() {
+ return mRequestType;
+ }
}
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index 01f5a09..e39c732 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -75,6 +75,8 @@
// Handle to the current override request, null if none.
private OverrideRequest mRequest;
+ // Handle to the current base state override request, null if none.
+ private OverrideRequest mBaseStateRequest;
private boolean mStickyRequestsAllowed;
// The current request has outlived their process.
@@ -111,13 +113,23 @@
}
}
+ void addBaseStateRequest(@NonNull OverrideRequest request) {
+ OverrideRequest previousRequest = mBaseStateRequest;
+ mBaseStateRequest = request;
+ mListener.onStatusChanged(request, STATUS_ACTIVE);
+
+ if (previousRequest != null) {
+ cancelRequestLocked(previousRequest);
+ }
+ }
+
/**
* Cancels the request with the specified {@code token} and notifies the listener of all changes
* to request status as a result of this operation.
*/
void cancelRequest(@NonNull OverrideRequest request) {
// Either don't have a current request or attempting to cancel an already cancelled request
- if (!hasRequest(request.getToken())) {
+ if (!hasRequest(request.getToken(), request.getRequestType())) {
return;
}
cancelCurrentRequestLocked();
@@ -144,11 +156,24 @@
}
/**
+ * Cancels the current base state override request, this could be due to the physical
+ * configuration of the device changing.
+ */
+ void cancelBaseStateOverrideRequest() {
+ cancelCurrentBaseStateRequestLocked();
+ }
+
+ /**
* Returns {@code true} if this controller is current managing a request with the specified
* {@code token}, {@code false} otherwise.
*/
- boolean hasRequest(@NonNull IBinder token) {
- return mRequest != null && token == mRequest.getToken();
+ boolean hasRequest(@NonNull IBinder token,
+ @OverrideRequest.OverrideRequestType int requestType) {
+ if (requestType == OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE) {
+ return mBaseStateRequest != null && token == mBaseStateRequest.getToken();
+ } else {
+ return mRequest != null && token == mRequest.getToken();
+ }
}
/**
@@ -157,11 +182,11 @@
* operation.
*/
void handleProcessDied(int pid) {
- if (mRequest == null) {
- return;
+ if (mBaseStateRequest != null && mBaseStateRequest.getPid() == pid) {
+ cancelCurrentBaseStateRequestLocked();
}
- if (mRequest.getPid() == pid) {
+ if (mRequest != null && mRequest.getPid() == pid) {
if (mStickyRequestsAllowed) {
// Do not cancel the requests now because sticky requests are allowed. These
// requests will be cancelled on a call to cancelStickyRequests().
@@ -176,7 +201,10 @@
* Notifies the controller that the base state has changed. The controller will notify the
* listener of all changes to request status as a result of this change.
*/
- void handleBaseStateChanged() {
+ void handleBaseStateChanged(int state) {
+ if (mBaseStateRequest != null && state != mBaseStateRequest.getRequestedState()) {
+ cancelBaseStateOverrideRequest();
+ }
if (mRequest == null) {
return;
}
@@ -192,11 +220,12 @@
* notify the listener of all changes to request status as a result of this change.
*/
void handleNewSupportedStates(int[] newSupportedStates) {
- if (mRequest == null) {
- return;
+ if (mBaseStateRequest != null && !contains(newSupportedStates,
+ mBaseStateRequest.getRequestedState())) {
+ cancelCurrentBaseStateRequestLocked();
}
- if (!contains(newSupportedStates, mRequest.getRequestedState())) {
+ if (mRequest != null && !contains(newSupportedStates, mRequest.getRequestedState())) {
cancelCurrentRequestLocked();
}
}
@@ -228,10 +257,23 @@
return;
}
mStickyRequest = false;
- mListener.onStatusChanged(mRequest, STATUS_CANCELED);
+ cancelRequestLocked(mRequest);
mRequest = null;
}
+ /**
+ * Handles cancelling {@code mBaseStateRequest}.
+ * Notifies the listener of the canceled status as well.
+ */
+ private void cancelCurrentBaseStateRequestLocked() {
+ if (mBaseStateRequest == null) {
+ Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
+ return;
+ }
+ cancelRequestLocked(mBaseStateRequest);
+ mBaseStateRequest = null;
+ }
+
private static boolean contains(int[] array, int value) {
for (int i = 0; i < array.length; i++) {
if (array[i] == value) {
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index 2f67ddd..372bc8a 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -31,7 +31,6 @@
import android.opengl.EGLSurface;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
-import android.os.IBinder;
import android.util.Slog;
import android.view.Display;
import android.view.DisplayInfo;
@@ -170,15 +169,7 @@
mDisplayWidth = displayInfo.getNaturalWidth();
mDisplayHeight = displayInfo.getNaturalHeight();
- final IBinder token = SurfaceControl.getInternalDisplayToken();
- if (token == null) {
- Slog.e(TAG,
- "Failed to take screenshot because internal display is disconnected");
- return false;
- }
- final boolean isWideColor = SurfaceControl.getDynamicDisplayInfo(token).activeColorMode
- == Display.COLOR_MODE_DISPLAY_P3;
-
+ final boolean isWideColor = displayInfo.colorMode == Display.COLOR_MODE_DISPLAY_P3;
// Set mPrepared here so if initialization fails, resources can be cleaned up.
mPrepared = true;
diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java
index e9640cf..a060f07 100644
--- a/services/core/java/com/android/server/display/DisplayControl.java
+++ b/services/core/java/com/android/server/display/DisplayControl.java
@@ -16,6 +16,9 @@
package com.android.server.display;
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.os.IBinder;
import java.util.Objects;
@@ -26,6 +29,7 @@
public class DisplayControl {
private static native IBinder nativeCreateDisplay(String name, boolean secure);
private static native void nativeDestroyDisplay(IBinder displayToken);
+ private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
/**
* Create a display in SurfaceFlinger.
@@ -52,4 +56,11 @@
nativeDestroyDisplay(displayToken);
}
+ /**
+ * Overrides HDR modes for a display device.
+ */
+ @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER)
+ public static void overrideHdrTypes(@NonNull IBinder displayToken, @NonNull int[] modes) {
+ nativeOverrideHdrTypes(displayToken, modes);
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 3a037ed..2cde526 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2562,10 +2562,10 @@
final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore,
display, mSyncRoot);
- final DisplayPowerController displayPowerController;
+ final DisplayPowerControllerInterface displayPowerController;
if (SystemProperties.getInt(PROP_USE_NEW_DISPLAY_POWER_CONTROLLER, 0) == 1) {
- displayPowerController = new DisplayPowerController(
+ displayPowerController = new DisplayPowerController2(
mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
() -> handleBrightnessChange(display));
@@ -3000,6 +3000,19 @@
}
}
+ @Override
+ public void overrideHdrTypes(int displayId, int[] modes) {
+ IBinder displayToken;
+ synchronized (mSyncRoot) {
+ displayToken = getDisplayToken(displayId);
+ if (displayToken == null) {
+ throw new IllegalArgumentException("Invalid display: " + displayId);
+ }
+ }
+
+ DisplayControl.overrideHdrTypes(displayToken, modes);
+ }
+
@Override // Binder call
public void setAreUserDisabledHdrTypesAllowed(boolean areUserDisabledHdrTypesAllowed) {
mContext.enforceCallingOrSelfPermission(
@@ -3884,6 +3897,19 @@
return displayIdToMirror;
}
}
+
+ @Override
+ public SurfaceControl.DisplayPrimaries getDisplayNativePrimaries(int displayId) {
+ IBinder displayToken;
+ synchronized (mSyncRoot) {
+ displayToken = getDisplayToken(displayId);
+ if (displayToken == null) {
+ throw new IllegalArgumentException("Invalid displayId=" + displayId);
+ }
+ }
+
+ return SurfaceControl.getDisplayNativePrimaries(displayToken);
+ }
}
class DesiredDisplayModeSpecsObserver
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 58a80e3..61225d7 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -798,8 +798,9 @@
* Notified when the display is changed. We use this to apply any changes that might be needed
* when displays get swapped on foldable devices. For example, different brightness properties
* of each display need to be properly reflected in AutomaticBrightnessController.
+ *
+ * Make sure DisplayManagerService.mSyncRoot is held when this is called
*/
- @GuardedBy("DisplayManagerService.mSyncRoot")
@Override
public void onDisplayChanged() {
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
@@ -988,8 +989,8 @@
com.android.internal.R.array.config_screenBrighteningThresholds);
int[] screenDarkeningThresholds = resources.getIntArray(
com.android.internal.R.array.config_screenDarkeningThresholds);
- int[] screenThresholdLevels = resources.getIntArray(
- com.android.internal.R.array.config_screenThresholdLevels);
+ float[] screenThresholdLevels = BrightnessMappingStrategy.getFloatArray(resources
+ .obtainTypedArray(com.android.internal.R.array.config_screenThresholdLevels));
float screenDarkeningMinThreshold =
mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
float screenBrighteningMinThreshold =
@@ -1896,7 +1897,7 @@
}
},
() -> {
- sendUpdatePowerStateLocked();
+ sendUpdatePowerState();
postBrightnessChangeRunnable();
// TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
if (mAutomaticBrightnessController != null) {
@@ -1912,7 +1913,7 @@
ddConfig != null ? ddConfig.getBrightnessThrottlingData() : null;
return new BrightnessThrottler(mHandler, data,
() -> {
- sendUpdatePowerStateLocked();
+ sendUpdatePowerState();
postBrightnessChangeRunnable();
}, mUniqueDisplayId);
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
new file mode 100644
index 0000000..dc7db10
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -0,0 +1,3071 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.AmbientBrightnessDayStats;
+import android.hardware.display.BrightnessChangeEvent;
+import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.metrics.LogMaker;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.FloatProperty;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.MutableFloat;
+import android.util.MutableInt;
+import android.util.Slog;
+import android.util.TimeUtils;
+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.app.IBatteryStats;
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.RingBuffer;
+import com.android.server.LocalServices;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.display.RampAnimator.DualRampAnimator;
+import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
+import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
+import com.android.server.display.utils.SensorUtils;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceSettings;
+import com.android.server.policy.WindowManagerPolicy;
+
+import java.io.PrintWriter;
+
+/**
+ * Controls the power state of the display.
+ *
+ * Handles the proximity sensor, light sensor, and animations between states
+ * including the screen off animation.
+ *
+ * This component acts independently of the rest of the power manager service.
+ * In particular, it does not share any state and it only communicates
+ * via asynchronous callbacks to inform the power manager that something has
+ * changed.
+ *
+ * Everything this class does internally is serialized on its handler although
+ * it may be accessed by other threads from the outside.
+ *
+ * Note that the power manager service guarantees that it will hold a suspend
+ * blocker as long as the display is not ready. So most of the work done here
+ * does not need to worry about holding a suspend blocker unless it happens
+ * independently of the display ready signal.
+ *
+ * For debugging, you can make the color fade and brightness animations run
+ * slower by changing the "animator duration scale" option in Development Settings.
+ */
+final class DisplayPowerController2 implements AutomaticBrightnessController.Callbacks,
+ DisplayWhiteBalanceController.Callbacks, DisplayPowerControllerInterface {
+ private static final String SCREEN_ON_BLOCKED_TRACE_NAME = "Screen on blocked";
+ private static final String SCREEN_OFF_BLOCKED_TRACE_NAME = "Screen off blocked";
+
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
+
+ // If true, uses the color fade on animation.
+ // We might want to turn this off if we cannot get a guarantee that the screen
+ // actually turns on and starts showing new content after the call to set the
+ // screen state returns. Playing the animation can also be somewhat slow.
+ private static final boolean USE_COLOR_FADE_ON_ANIMATION = false;
+
+ private static final float SCREEN_ANIMATION_RATE_MINIMUM = 0.0f;
+
+ private static final int COLOR_FADE_ON_ANIMATION_DURATION_MILLIS = 250;
+ private static final int COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS = 400;
+
+ private static final int MSG_UPDATE_POWER_STATE = 1;
+ private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 2;
+ private static final int MSG_SCREEN_ON_UNBLOCKED = 3;
+ private static final int MSG_SCREEN_OFF_UNBLOCKED = 4;
+ private static final int MSG_CONFIGURE_BRIGHTNESS = 5;
+ private static final int MSG_SET_TEMPORARY_BRIGHTNESS = 6;
+ private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 7;
+ private static final int MSG_IGNORE_PROXIMITY = 8;
+ private static final int MSG_STOP = 9;
+ private static final int MSG_UPDATE_BRIGHTNESS = 10;
+ private static final int MSG_UPDATE_RBC = 11;
+ private static final int MSG_BRIGHTNESS_RAMP_DONE = 12;
+ private static final int MSG_STATSD_HBM_BRIGHTNESS = 13;
+
+ private static final int PROXIMITY_UNKNOWN = -1;
+ private static final int PROXIMITY_NEGATIVE = 0;
+ private static final int PROXIMITY_POSITIVE = 1;
+
+ // Proximity sensor debounce delay in milliseconds for positive or negative transitions.
+ private static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
+ private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250;
+
+ private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
+
+ // Trigger proximity if distance is less than 5 cm.
+ private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f;
+
+ // State machine constants for tracking initial brightness ramp skipping when enabled.
+ private static final int RAMP_STATE_SKIP_NONE = 0;
+ private static final int RAMP_STATE_SKIP_INITIAL = 1;
+ private static final int RAMP_STATE_SKIP_AUTOBRIGHT = 2;
+
+ private static final int REPORTED_TO_POLICY_UNREPORTED = -1;
+ private static final int REPORTED_TO_POLICY_SCREEN_OFF = 0;
+ private static final int REPORTED_TO_POLICY_SCREEN_TURNING_ON = 1;
+ private static final int REPORTED_TO_POLICY_SCREEN_ON = 2;
+ private static final int REPORTED_TO_POLICY_SCREEN_TURNING_OFF = 3;
+
+ private static final int RINGBUFFER_MAX = 100;
+
+ private final String mTag;
+
+ private final Object mLock = new Object();
+
+ private final Context mContext;
+
+ // Our handler.
+ private final DisplayControllerHandler mHandler;
+
+ // Asynchronous callbacks into the power manager service.
+ // Only invoked from the handler thread while no locks are held.
+ private final DisplayPowerCallbacks mCallbacks;
+
+ // Battery stats.
+ @Nullable
+ private final IBatteryStats mBatteryStats;
+
+ // The sensor manager.
+ private final SensorManager mSensorManager;
+
+ // The window manager policy.
+ private final WindowManagerPolicy mWindowManagerPolicy;
+
+ // The display blanker.
+ private final DisplayBlanker mBlanker;
+
+ // The LogicalDisplay tied to this DisplayPowerController2.
+ private final LogicalDisplay mLogicalDisplay;
+
+ // The ID of the LogicalDisplay tied to this DisplayPowerController2.
+ private final int mDisplayId;
+
+ // The unique ID of the primary display device currently tied to this logical display
+ private String mUniqueDisplayId;
+
+ // Tracker for brightness changes.
+ @Nullable
+ private final BrightnessTracker mBrightnessTracker;
+
+ // Tracker for brightness settings changes.
+ private final SettingsObserver mSettingsObserver;
+
+ // The proximity sensor, or null if not available or needed.
+ private Sensor mProximitySensor;
+
+ // The doze screen brightness.
+ private final float mScreenBrightnessDozeConfig;
+
+ // The dim screen brightness.
+ private final float mScreenBrightnessDimConfig;
+
+ // The minimum dim amount to use if the screen brightness is already below
+ // mScreenBrightnessDimConfig.
+ private final float mScreenBrightnessMinimumDimAmount;
+
+ private final float mScreenBrightnessDefault;
+
+ // The minimum allowed brightness while in VR.
+ private final float mScreenBrightnessForVrRangeMinimum;
+
+ // The maximum allowed brightness while in VR.
+ private final float mScreenBrightnessForVrRangeMaximum;
+
+ // The default screen brightness for VR.
+ private final float mScreenBrightnessForVrDefault;
+
+ // True if auto-brightness should be used.
+ private boolean mUseSoftwareAutoBrightnessConfig;
+
+ // True if should use light sensor to automatically determine doze screen brightness.
+ private final boolean mAllowAutoBrightnessWhileDozingConfig;
+
+ // Whether or not the color fade on screen on / off is enabled.
+ private final boolean mColorFadeEnabled;
+
+ @GuardedBy("mCachedBrightnessInfo")
+ private final CachedBrightnessInfo mCachedBrightnessInfo = new CachedBrightnessInfo();
+
+ private DisplayDevice mDisplayDevice;
+
+ // True if we should fade the screen while turning it off, false if we should play
+ // a stylish color fade animation instead.
+ private final boolean mColorFadeFadesConfig;
+
+ // True if we need to fake a transition to off when coming out of a doze state.
+ // Some display hardware will blank itself when coming out of doze in order to hide
+ // artifacts. For these displays we fake a transition into OFF so that policy can appropriately
+ // blank itself and begin an appropriate power on animation.
+ private final boolean mDisplayBlanksAfterDozeConfig;
+
+ // True if there are only buckets of brightness values when the display is in the doze state,
+ // rather than a full range of values. If this is true, then we'll avoid animating the screen
+ // brightness since it'd likely be multiple jarring brightness transitions instead of just one
+ // to reach the final state.
+ private final boolean mBrightnessBucketsInDozeConfig;
+
+ private final Clock mClock;
+ private final Injector mInjector;
+
+ // Maximum time a ramp animation can take.
+ private long mBrightnessRampIncreaseMaxTimeMillis;
+ private long mBrightnessRampDecreaseMaxTimeMillis;
+
+ // The pending power request.
+ // Initially null until the first call to requestPowerState.
+ @GuardedBy("mLock")
+ private DisplayPowerRequest mPendingRequestLocked;
+
+ // True if a request has been made to wait for the proximity sensor to go negative.
+ @GuardedBy("mLock")
+ private boolean mPendingWaitForNegativeProximityLocked;
+
+ // True if the pending power request or wait for negative proximity flag
+ // has been changed since the last update occurred.
+ @GuardedBy("mLock")
+ private boolean mPendingRequestChangedLocked;
+
+ // Set to true when the important parts of the pending power request have been applied.
+ // The important parts are mainly the screen state. Brightness changes may occur
+ // concurrently.
+ @GuardedBy("mLock")
+ private boolean mDisplayReadyLocked;
+
+ // Set to true if a power state update is required.
+ @GuardedBy("mLock")
+ private boolean mPendingUpdatePowerStateLocked;
+
+ /* The following state must only be accessed by the handler thread. */
+
+ // The currently requested power state.
+ // The power controller will progressively update its internal state to match
+ // the requested power state. Initially null until the first update.
+ private DisplayPowerRequest mPowerRequest;
+
+ // The current power state.
+ // Must only be accessed on the handler thread.
+ private DisplayPowerState mPowerState;
+
+ // True if the device should wait for negative proximity sensor before
+ // waking up the screen. This is set to false as soon as a negative
+ // proximity sensor measurement is observed or when the device is forced to
+ // go to sleep by the user. While true, the screen remains off.
+ private boolean mWaitingForNegativeProximity;
+
+ // True if the device should not take into account the proximity sensor
+ // until either the proximity sensor state changes, or there is no longer a
+ // request to listen to proximity sensor.
+ private boolean mIgnoreProximityUntilChanged;
+
+ // The actual proximity sensor threshold value.
+ private float mProximityThreshold;
+
+ // Set to true if the proximity sensor listener has been registered
+ // with the sensor manager.
+ private boolean mProximitySensorEnabled;
+
+ // The debounced proximity sensor state.
+ private int mProximity = PROXIMITY_UNKNOWN;
+
+ // The raw non-debounced proximity sensor state.
+ private int mPendingProximity = PROXIMITY_UNKNOWN;
+ private long mPendingProximityDebounceTime = -1; // -1 if fully debounced
+
+ // True if the screen was turned off because of the proximity sensor.
+ // When the screen turns on again, we report user activity to the power manager.
+ private boolean mScreenOffBecauseOfProximity;
+
+ // The currently active screen on unblocker. This field is non-null whenever
+ // we are waiting for a callback to release it and unblock the screen.
+ private ScreenOnUnblocker mPendingScreenOnUnblocker;
+ private ScreenOffUnblocker mPendingScreenOffUnblocker;
+
+ // True if we were in the process of turning off the screen.
+ // This allows us to recover more gracefully from situations where we abort
+ // turning off the screen.
+ private boolean mPendingScreenOff;
+
+ // True if we have unfinished business and are holding a suspend blocker.
+ private boolean mUnfinishedBusiness;
+
+ // The elapsed real time when the screen on was blocked.
+ private long mScreenOnBlockStartRealTime;
+ private long mScreenOffBlockStartRealTime;
+
+ // Screen state we reported to policy. Must be one of REPORTED_TO_POLICY_* fields.
+ private int mReportedScreenStateToPolicy = REPORTED_TO_POLICY_UNREPORTED;
+
+ // If the last recorded screen state was dozing or not.
+ private boolean mDozing;
+
+ // Remembers whether certain kinds of brightness adjustments
+ // were recently applied so that we can decide how to transition.
+ private boolean mAppliedAutoBrightness;
+ private boolean mAppliedDimming;
+ private boolean mAppliedLowPower;
+ private boolean mAppliedScreenBrightnessOverride;
+ private boolean mAppliedTemporaryBrightness;
+ private boolean mAppliedTemporaryAutoBrightnessAdjustment;
+ private boolean mAppliedBrightnessBoost;
+ private boolean mAppliedThrottling;
+
+ // Reason for which the brightness was last changed. See {@link BrightnessReason} for more
+ // information.
+ // At the time of this writing, this value is changed within updatePowerState() only, which is
+ // limited to the thread used by DisplayControllerHandler.
+ private final BrightnessReason mBrightnessReason = new BrightnessReason();
+ private final BrightnessReason mBrightnessReasonTemp = new BrightnessReason();
+
+ // Brightness animation ramp rates in brightness units per second
+ private float mBrightnessRampRateFastDecrease;
+ private float mBrightnessRampRateFastIncrease;
+ private float mBrightnessRampRateSlowDecrease;
+ private float mBrightnessRampRateSlowIncrease;
+
+ // Report HBM brightness change to StatsD
+ private int mDisplayStatsId;
+ private float mLastStatsBrightness = PowerManager.BRIGHTNESS_MIN;
+
+ // Whether or not to skip the initial brightness ramps into STATE_ON.
+ private final boolean mSkipScreenOnBrightnessRamp;
+
+ // Display white balance components.
+ @Nullable
+ private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings;
+ @Nullable
+ private final DisplayWhiteBalanceController mDisplayWhiteBalanceController;
+
+ @Nullable
+ private final ColorDisplayServiceInternal mCdsi;
+ private float[] mNitsRange;
+
+ private final HighBrightnessModeController mHbmController;
+
+ private final BrightnessThrottler mBrightnessThrottler;
+
+ private final BrightnessSetting mBrightnessSetting;
+
+ private final Runnable mOnBrightnessChangeRunnable;
+
+ private final BrightnessEvent mLastBrightnessEvent;
+ private final BrightnessEvent mTempBrightnessEvent;
+
+ // Keeps a record of brightness changes for dumpsys.
+ private RingBuffer<BrightnessEvent> mBrightnessEventRingBuffer;
+
+ // A record of state for skipping brightness ramps.
+ private int mSkipRampState = RAMP_STATE_SKIP_NONE;
+
+ // The first autobrightness value set when entering RAMP_STATE_SKIP_INITIAL.
+ private float mInitialAutoBrightness;
+
+ // The controller for the automatic brightness level.
+ @Nullable
+ private AutomaticBrightnessController mAutomaticBrightnessController;
+
+ private Sensor mLightSensor;
+
+ // The mappers between ambient lux, display backlight values, and display brightness.
+ // We will switch between the idle mapper and active mapper in AutomaticBrightnessController.
+ // Mapper used for active (normal) screen brightness mode
+ @Nullable
+ private BrightnessMappingStrategy mInteractiveModeBrightnessMapper;
+ // Mapper used for idle screen brightness mode
+ @Nullable
+ private BrightnessMappingStrategy mIdleModeBrightnessMapper;
+
+ // The current brightness configuration.
+ @Nullable
+ private BrightnessConfiguration mBrightnessConfiguration;
+
+ // The last brightness that was set by the user and not temporary. Set to
+ // PowerManager.BRIGHTNESS_INVALID_FLOAT when a brightness has yet to be recorded.
+ private float mLastUserSetScreenBrightness = Float.NaN;
+
+ // The screen brightness setting has changed but not taken effect yet. If this is different
+ // from the current screen brightness setting then this is coming from something other than us
+ // and should be considered a user interaction.
+ private float mPendingScreenBrightnessSetting;
+
+ // The last observed screen brightness setting, either set by us or by the settings app on
+ // behalf of the user.
+ private float mCurrentScreenBrightnessSetting;
+
+ // The temporary screen brightness. Typically set when a user is interacting with the
+ // brightness slider but hasn't settled on a choice yet. Set to
+ // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary brightness set.
+ private float mTemporaryScreenBrightness;
+
+ // The current screen brightness while in VR mode.
+ private float mScreenBrightnessForVr;
+
+ // The last auto brightness adjustment that was set by the user and not temporary. Set to
+ // Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
+ private float mAutoBrightnessAdjustment;
+
+ // The pending auto brightness adjustment that will take effect on the next power state update.
+ private float mPendingAutoBrightnessAdjustment;
+
+ // The temporary auto brightness adjustment. Typically set when a user is interacting with the
+ // adjustment slider but hasn't settled on a choice yet. Set to
+ // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
+ private float mTemporaryAutoBrightnessAdjustment;
+
+ private boolean mIsRbcActive;
+
+ // Whether there's a callback to tell listeners the display has changed scheduled to run. When
+ // true it implies a wakelock is being held to guarantee the update happens before we collapse
+ // into suspend and so needs to be cleaned up if the thread is exiting.
+ // Should only be accessed on the Handler thread.
+ private boolean mOnStateChangedPending;
+
+ // Count of proximity messages currently on this DPC's Handler. Used to keep track of how many
+ // suspend blocker acquisitions are pending when shutting down this DPC.
+ // Should only be accessed on the Handler thread.
+ private int mOnProximityPositiveMessages;
+ private int mOnProximityNegativeMessages;
+
+ // Animators.
+ private ObjectAnimator mColorFadeOnAnimator;
+ private ObjectAnimator mColorFadeOffAnimator;
+ private DualRampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
+ private BrightnessSetting.BrightnessSettingListener mBrightnessSettingListener;
+
+ // True if this DisplayPowerController2 has been stopped and should no longer be running.
+ private boolean mStopped;
+
+ private DisplayDeviceConfig mDisplayDeviceConfig;
+
+ // Identifiers for suspend blocker acuisition requests
+ private final String mSuspendBlockerIdUnfinishedBusiness;
+ private final String mSuspendBlockerIdOnStateChanged;
+ private final String mSuspendBlockerIdProxPositive;
+ private final String mSuspendBlockerIdProxNegative;
+ private final String mSuspendBlockerIdProxDebounce;
+
+ /**
+ * Creates the display power controller.
+ */
+ DisplayPowerController2(Context context, Injector injector,
+ DisplayPowerCallbacks callbacks, Handler handler,
+ SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
+ BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
+ Runnable onBrightnessChangeRunnable) {
+
+ mInjector = injector != null ? injector : new Injector();
+ mClock = mInjector.getClock();
+ mLogicalDisplay = logicalDisplay;
+ mDisplayId = mLogicalDisplay.getDisplayIdLocked();
+ mTag = "DisplayPowerController2[" + mDisplayId + "]";
+ mSuspendBlockerIdUnfinishedBusiness = getSuspendBlockerUnfinishedBusinessId(mDisplayId);
+ mSuspendBlockerIdOnStateChanged = getSuspendBlockerOnStateChangedId(mDisplayId);
+ mSuspendBlockerIdProxPositive = getSuspendBlockerProxPositiveId(mDisplayId);
+ mSuspendBlockerIdProxNegative = getSuspendBlockerProxNegativeId(mDisplayId);
+ mSuspendBlockerIdProxDebounce = getSuspendBlockerProxDebounceId(mDisplayId);
+
+ mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+ mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
+ mDisplayStatsId = mUniqueDisplayId.hashCode();
+ mHandler = new DisplayControllerHandler(handler.getLooper());
+ mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
+ mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
+
+ if (mDisplayId == Display.DEFAULT_DISPLAY) {
+ mBatteryStats = BatteryStatsService.getService();
+ } else {
+ mBatteryStats = null;
+ }
+
+ mSettingsObserver = new SettingsObserver(mHandler);
+ mCallbacks = callbacks;
+ mSensorManager = sensorManager;
+ mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class);
+ mBlanker = blanker;
+ mContext = context;
+ mBrightnessTracker = brightnessTracker;
+ // TODO: b/186428377 update brightness setting when display changes
+ mBrightnessSetting = brightnessSetting;
+ mOnBrightnessChangeRunnable = onBrightnessChangeRunnable;
+
+ PowerManager pm = context.getSystemService(PowerManager.class);
+
+ final Resources resources = context.getResources();
+
+ // DOZE AND DIM SETTINGS
+ mScreenBrightnessDozeConfig = clampAbsoluteBrightness(
+ pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE));
+ mScreenBrightnessDimConfig = clampAbsoluteBrightness(
+ pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
+ mScreenBrightnessMinimumDimAmount = resources.getFloat(
+ R.dimen.config_screenBrightnessMinimumDimAmountFloat);
+
+
+ // NORMAL SCREEN SETTINGS
+ mScreenBrightnessDefault = clampAbsoluteBrightness(
+ mLogicalDisplay.getDisplayInfoLocked().brightnessDefault);
+
+ // VR SETTINGS
+ mScreenBrightnessForVrDefault = clampAbsoluteBrightness(
+ pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT_VR));
+ mScreenBrightnessForVrRangeMaximum = clampAbsoluteBrightness(
+ pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM_VR));
+ mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness(
+ pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR));
+
+ // Check the setting, but also verify that it is the default display. Only the default
+ // display has an automatic brightness controller running.
+ // TODO: b/179021925 - Fix to work with multiple displays
+ mUseSoftwareAutoBrightnessConfig = resources.getBoolean(
+ R.bool.config_automatic_brightness_available)
+ && mDisplayId == Display.DEFAULT_DISPLAY;
+
+ mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
+ R.bool.config_allowAutoBrightnessWhileDozing);
+
+ mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked()
+ .getDisplayDeviceConfig();
+
+ loadBrightnessRampRates();
+ mSkipScreenOnBrightnessRamp = resources.getBoolean(
+ R.bool.config_skipScreenOnBrightnessRamp);
+
+ mHbmController = createHbmControllerLocked();
+
+ mBrightnessThrottler = createBrightnessThrottlerLocked();
+
+ // Seed the cached brightness
+ saveBrightnessInfo(getScreenBrightnessSetting());
+
+ DisplayWhiteBalanceSettings displayWhiteBalanceSettings = null;
+ DisplayWhiteBalanceController displayWhiteBalanceController = null;
+ if (mDisplayId == Display.DEFAULT_DISPLAY) {
+ try {
+ displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler);
+ displayWhiteBalanceController = DisplayWhiteBalanceFactory.create(mHandler,
+ mSensorManager, resources);
+ displayWhiteBalanceSettings.setCallbacks(this);
+ displayWhiteBalanceController.setCallbacks(this);
+ } catch (Exception e) {
+ Slog.e(mTag, "failed to set up display white-balance: " + e);
+ }
+ }
+ mDisplayWhiteBalanceSettings = displayWhiteBalanceSettings;
+ mDisplayWhiteBalanceController = displayWhiteBalanceController;
+
+ loadNitsRange(resources);
+
+ if (mDisplayId == Display.DEFAULT_DISPLAY) {
+ mCdsi = LocalServices.getService(ColorDisplayServiceInternal.class);
+ boolean active = mCdsi.setReduceBrightColorsListener(new ReduceBrightColorsListener() {
+ @Override
+ public void onReduceBrightColorsActivationChanged(boolean activated,
+ boolean userInitiated) {
+ applyReduceBrightColorsSplineAdjustment();
+
+ }
+
+ @Override
+ public void onReduceBrightColorsStrengthChanged(int strength) {
+ applyReduceBrightColorsSplineAdjustment();
+ }
+ });
+ if (active) {
+ applyReduceBrightColorsSplineAdjustment();
+ }
+ } else {
+ mCdsi = null;
+ }
+
+ setUpAutoBrightness(resources, handler);
+
+ mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic();
+ mColorFadeFadesConfig = resources.getBoolean(
+ R.bool.config_animateScreenLights);
+
+ mDisplayBlanksAfterDozeConfig = resources.getBoolean(
+ R.bool.config_displayBlanksAfterDoze);
+
+ mBrightnessBucketsInDozeConfig = resources.getBoolean(
+ R.bool.config_displayBrightnessBucketsInDoze);
+
+ loadProximitySensor();
+
+ mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
+ mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
+ mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+ mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+
+ }
+
+ private void applyReduceBrightColorsSplineAdjustment() {
+ mHandler.obtainMessage(MSG_UPDATE_RBC).sendToTarget();
+ sendUpdatePowerState();
+ }
+
+ private void handleRbcChanged() {
+ if (mAutomaticBrightnessController == null) {
+ return;
+ }
+ if ((!mAutomaticBrightnessController.isInIdleMode()
+ && mInteractiveModeBrightnessMapper == null)
+ || (mAutomaticBrightnessController.isInIdleMode()
+ && mIdleModeBrightnessMapper == null)) {
+ Log.w(mTag, "No brightness mapping available to recalculate splines for this mode");
+ return;
+ }
+
+ float[] adjustedNits = new float[mNitsRange.length];
+ for (int i = 0; i < mNitsRange.length; i++) {
+ adjustedNits[i] = mCdsi.getReduceBrightColorsAdjustedBrightnessNits(mNitsRange[i]);
+ }
+ mIsRbcActive = mCdsi.isReduceBrightColorsActivated();
+ mAutomaticBrightnessController.recalculateSplines(mIsRbcActive, adjustedNits);
+ }
+
+ /**
+ * Returns true if the proximity sensor screen-off function is available.
+ */
+ @Override
+ public boolean isProximitySensorAvailable() {
+ return mProximitySensor != null;
+ }
+
+ /**
+ * Get the {@link BrightnessChangeEvent}s for the specified user.
+ *
+ * @param userId userId to fetch data for
+ * @param includePackage if false will null out the package name in events
+ */
+ @Nullable
+ @Override
+ public ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents(
+ @UserIdInt int userId, boolean includePackage) {
+ if (mBrightnessTracker == null) {
+ return null;
+ }
+ return mBrightnessTracker.getEvents(userId, includePackage);
+ }
+
+ @Override
+ public void onSwitchUser(@UserIdInt int newUserId) {
+ handleSettingsChange(true /* userSwitch */);
+ if (mBrightnessTracker != null) {
+ mBrightnessTracker.onSwitchUser(newUserId);
+ }
+ }
+
+ @Nullable
+ @Override
+ public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(
+ @UserIdInt int userId) {
+ if (mBrightnessTracker == null) {
+ return null;
+ }
+ return mBrightnessTracker.getAmbientBrightnessStats(userId);
+ }
+
+ /**
+ * Persist the brightness slider events and ambient brightness stats to disk.
+ */
+ @Override
+ public void persistBrightnessTrackerState() {
+ if (mBrightnessTracker != null) {
+ mBrightnessTracker.persistBrightnessTrackerState();
+ }
+ }
+
+ /**
+ * Requests a new power state.
+ * The controller makes a copy of the provided object and then
+ * begins adjusting the power state to match what was requested.
+ *
+ * @param request The requested power state.
+ * @param waitForNegativeProximity If true, issues a request to wait for
+ * negative proximity before turning the screen back on,
+ * assuming the screen
+ * was turned off by the proximity sensor.
+ * @return True if display is ready, false if there are important changes that must
+ * be made asynchronously (such as turning the screen on), in which case the caller
+ * should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged()}
+ * then try the request again later until the state converges.
+ */
+ public boolean requestPowerState(DisplayPowerRequest request,
+ boolean waitForNegativeProximity) {
+ if (DEBUG) {
+ Slog.d(mTag, "requestPowerState: "
+ + request + ", waitForNegativeProximity=" + waitForNegativeProximity);
+ }
+
+ synchronized (mLock) {
+ if (mStopped) {
+ return true;
+ }
+
+ boolean changed = false;
+
+ if (waitForNegativeProximity
+ && !mPendingWaitForNegativeProximityLocked) {
+ mPendingWaitForNegativeProximityLocked = true;
+ changed = true;
+ }
+
+ if (mPendingRequestLocked == null) {
+ mPendingRequestLocked = new DisplayPowerRequest(request);
+ changed = true;
+ } else if (!mPendingRequestLocked.equals(request)) {
+ mPendingRequestLocked.copyFrom(request);
+ changed = true;
+ }
+
+ if (changed) {
+ mDisplayReadyLocked = false;
+ if (!mPendingRequestChangedLocked) {
+ mPendingRequestChangedLocked = true;
+ sendUpdatePowerStateLocked();
+ }
+ }
+
+ return mDisplayReadyLocked;
+ }
+ }
+
+ @Override
+ public BrightnessConfiguration getDefaultBrightnessConfiguration() {
+ if (mAutomaticBrightnessController == null) {
+ return null;
+ }
+ return mAutomaticBrightnessController.getDefaultConfig();
+ }
+
+ /**
+ * Notified when the display is changed. We use this to apply any changes that might be needed
+ * when displays get swapped on foldable devices. For example, different brightness properties
+ * of each display need to be properly reflected in AutomaticBrightnessController.
+ *
+ * Make sure DisplayManagerService.mSyncRoot lock is held when this is called
+ */
+ @Override
+ public void onDisplayChanged() {
+ final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+ if (device == null) {
+ Slog.wtf(mTag, "Display Device is null in DisplayPowerController2 for display: "
+ + mLogicalDisplay.getDisplayIdLocked());
+ return;
+ }
+
+ final String uniqueId = device.getUniqueId();
+ final DisplayDeviceConfig config = device.getDisplayDeviceConfig();
+ final IBinder token = device.getDisplayTokenLocked();
+ final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+ mHandler.post(() -> {
+ if (mDisplayDevice != device) {
+ mDisplayDevice = device;
+ mUniqueDisplayId = uniqueId;
+ mDisplayStatsId = mUniqueDisplayId.hashCode();
+ mDisplayDeviceConfig = config;
+ loadFromDisplayDeviceConfig(token, info);
+ updatePowerState();
+ }
+ });
+ }
+
+ /**
+ * Called when the displays are preparing to transition from one device state to another.
+ * This process involves turning off some displays so we need updatePowerState() to run and
+ * calculate the new state.
+ */
+ @Override
+ public void onDeviceStateTransition() {
+ sendUpdatePowerState();
+ }
+
+ /**
+ * Unregisters all listeners and interrupts all running threads; halting future work.
+ *
+ * This method should be called when the DisplayPowerController2 is no longer in use; i.e. when
+ * the {@link #mDisplayId display} has been removed.
+ */
+ @Override
+ public void stop() {
+ synchronized (mLock) {
+ mStopped = true;
+ Message msg = mHandler.obtainMessage(MSG_STOP);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
+
+ if (mDisplayWhiteBalanceController != null) {
+ mDisplayWhiteBalanceController.setEnabled(false);
+ }
+
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.stop();
+ }
+
+ if (mBrightnessSetting != null) {
+ mBrightnessSetting.unregisterListener(mBrightnessSettingListener);
+ }
+
+ mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
+ }
+ }
+
+ private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info) {
+ // All properties that depend on the associated DisplayDevice and the DDC must be
+ // updated here.
+ loadBrightnessRampRates();
+ loadProximitySensor();
+ loadNitsRange(mContext.getResources());
+ setUpAutoBrightness(mContext.getResources(), mHandler);
+ reloadReduceBrightColours();
+ if (mScreenBrightnessRampAnimator != null) {
+ mScreenBrightnessRampAnimator.setAnimationTimeLimits(
+ mBrightnessRampIncreaseMaxTimeMillis,
+ mBrightnessRampDecreaseMaxTimeMillis);
+ }
+ mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
+ mDisplayDeviceConfig.getHighBrightnessModeData(),
+ new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
+ @Override
+ public float getHdrBrightnessFromSdr(float sdrBrightness) {
+ return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness);
+ }
+ });
+ mBrightnessThrottler.resetThrottlingData(
+ mDisplayDeviceConfig.getBrightnessThrottlingData(), mUniqueDisplayId);
+ }
+
+ private void sendUpdatePowerState() {
+ synchronized (mLock) {
+ sendUpdatePowerStateLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void sendUpdatePowerStateLocked() {
+ if (!mStopped && !mPendingUpdatePowerStateLocked) {
+ mPendingUpdatePowerStateLocked = true;
+ Message msg = mHandler.obtainMessage(MSG_UPDATE_POWER_STATE);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
+ }
+ }
+
+ private void initialize(int displayState) {
+ mPowerState = mInjector.getDisplayPowerState(mBlanker,
+ mColorFadeEnabled ? new ColorFade(mDisplayId) : null, mDisplayId, displayState);
+
+ if (mColorFadeEnabled) {
+ mColorFadeOnAnimator = ObjectAnimator.ofFloat(
+ mPowerState, DisplayPowerState.COLOR_FADE_LEVEL, 0.0f, 1.0f);
+ mColorFadeOnAnimator.setDuration(COLOR_FADE_ON_ANIMATION_DURATION_MILLIS);
+ mColorFadeOnAnimator.addListener(mAnimatorListener);
+
+ mColorFadeOffAnimator = ObjectAnimator.ofFloat(
+ mPowerState, DisplayPowerState.COLOR_FADE_LEVEL, 1.0f, 0.0f);
+ mColorFadeOffAnimator.setDuration(COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS);
+ mColorFadeOffAnimator.addListener(mAnimatorListener);
+ }
+
+ mScreenBrightnessRampAnimator = mInjector.getDualRampAnimator(mPowerState,
+ DisplayPowerState.SCREEN_BRIGHTNESS_FLOAT,
+ DisplayPowerState.SCREEN_SDR_BRIGHTNESS_FLOAT);
+ mScreenBrightnessRampAnimator.setAnimationTimeLimits(
+ mBrightnessRampIncreaseMaxTimeMillis,
+ mBrightnessRampDecreaseMaxTimeMillis);
+ mScreenBrightnessRampAnimator.setListener(mRampAnimatorListener);
+
+ noteScreenState(mPowerState.getScreenState());
+ noteScreenBrightness(mPowerState.getScreenBrightness());
+
+ // Initialize all of the brightness tracking state
+ final float brightness = convertToNits(mPowerState.getScreenBrightness());
+ if (brightness >= PowerManager.BRIGHTNESS_MIN) {
+ mBrightnessTracker.start(brightness);
+ }
+ mBrightnessSettingListener = brightnessValue -> {
+ Message msg = mHandler.obtainMessage(MSG_UPDATE_BRIGHTNESS, brightnessValue);
+ mHandler.sendMessage(msg);
+ };
+
+ mBrightnessSetting.registerListener(mBrightnessSettingListener);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT),
+ false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
+ false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+ }
+
+ private void setUpAutoBrightness(Resources resources, Handler handler) {
+ if (!mUseSoftwareAutoBrightnessConfig) {
+ return;
+ }
+
+ final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
+ R.bool.config_enableIdleScreenBrightnessMode);
+ mInteractiveModeBrightnessMapper = BrightnessMappingStrategy.create(resources,
+ mDisplayDeviceConfig, mDisplayWhiteBalanceController);
+ if (isIdleScreenBrightnessEnabled) {
+ mIdleModeBrightnessMapper = BrightnessMappingStrategy.createForIdleMode(resources,
+ mDisplayDeviceConfig, mDisplayWhiteBalanceController);
+ }
+
+ if (mInteractiveModeBrightnessMapper != null) {
+ final float dozeScaleFactor = resources.getFraction(
+ R.fraction.config_screenAutoBrightnessDozeScaleFactor,
+ 1, 1);
+
+ int[] ambientBrighteningThresholds = resources.getIntArray(
+ R.array.config_ambientBrighteningThresholds);
+ int[] ambientDarkeningThresholds = resources.getIntArray(
+ R.array.config_ambientDarkeningThresholds);
+ int[] ambientThresholdLevels = resources.getIntArray(
+ R.array.config_ambientThresholdLevels);
+ float ambientDarkeningMinThreshold =
+ mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
+ float ambientBrighteningMinThreshold =
+ mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
+ HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels(
+ ambientBrighteningThresholds, ambientDarkeningThresholds,
+ ambientThresholdLevels, ambientDarkeningMinThreshold,
+ ambientBrighteningMinThreshold);
+
+ int[] screenBrighteningThresholds = resources.getIntArray(
+ R.array.config_screenBrighteningThresholds);
+ int[] screenDarkeningThresholds = resources.getIntArray(
+ R.array.config_screenDarkeningThresholds);
+ int[] screenThresholdLevels = resources.getIntArray(
+ R.array.config_screenThresholdLevels);
+ float screenDarkeningMinThreshold =
+ mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
+ float screenBrighteningMinThreshold =
+ mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
+ HysteresisLevels screenBrightnessThresholds = new HysteresisLevels(
+ screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
+ screenDarkeningMinThreshold, screenBrighteningMinThreshold);
+
+ // Idle screen thresholds
+ float screenDarkeningMinThresholdIdle =
+ mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
+ float screenBrighteningMinThresholdIdle =
+ mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
+ HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
+ screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
+ screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
+
+ // Idle ambient thresholds
+ float ambientDarkeningMinThresholdIdle =
+ mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle();
+ float ambientBrighteningMinThresholdIdle =
+ mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle();
+ HysteresisLevels ambientBrightnessThresholdsIdle = new HysteresisLevels(
+ ambientBrighteningThresholds, ambientDarkeningThresholds,
+ ambientThresholdLevels, ambientDarkeningMinThresholdIdle,
+ ambientBrighteningMinThresholdIdle);
+
+ long brighteningLightDebounce = mDisplayDeviceConfig
+ .getAutoBrightnessBrighteningLightDebounce();
+ long darkeningLightDebounce = mDisplayDeviceConfig
+ .getAutoBrightnessDarkeningLightDebounce();
+ boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean(
+ R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
+
+ int lightSensorWarmUpTimeConfig = resources.getInteger(
+ R.integer.config_lightSensorWarmupTime);
+ int lightSensorRate = resources.getInteger(
+ R.integer.config_autoBrightnessLightSensorRate);
+ int initialLightSensorRate = resources.getInteger(
+ R.integer.config_autoBrightnessInitialLightSensorRate);
+ if (initialLightSensorRate == -1) {
+ initialLightSensorRate = lightSensorRate;
+ } else if (initialLightSensorRate > lightSensorRate) {
+ Slog.w(mTag, "Expected config_autoBrightnessInitialLightSensorRate ("
+ + initialLightSensorRate + ") to be less than or equal to "
+ + "config_autoBrightnessLightSensorRate (" + lightSensorRate + ").");
+ }
+
+ loadAmbientLightSensor();
+ if (mBrightnessTracker != null) {
+ mBrightnessTracker.setLightSensor(mLightSensor);
+ }
+
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.stop();
+ }
+ mAutomaticBrightnessController = new AutomaticBrightnessController(this,
+ handler.getLooper(), mSensorManager, mLightSensor,
+ mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig,
+ PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor,
+ lightSensorRate, initialLightSensorRate, brighteningLightDebounce,
+ darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp,
+ ambientBrightnessThresholds, screenBrightnessThresholds,
+ ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext,
+ mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
+ mDisplayDeviceConfig.getAmbientHorizonShort(),
+ mDisplayDeviceConfig.getAmbientHorizonLong());
+
+ mBrightnessEventRingBuffer =
+ new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
+ } else {
+ mUseSoftwareAutoBrightnessConfig = false;
+ }
+ }
+
+ private void loadBrightnessRampRates() {
+ mBrightnessRampRateFastDecrease = mDisplayDeviceConfig.getBrightnessRampFastDecrease();
+ mBrightnessRampRateFastIncrease = mDisplayDeviceConfig.getBrightnessRampFastIncrease();
+ mBrightnessRampRateSlowDecrease = mDisplayDeviceConfig.getBrightnessRampSlowDecrease();
+ mBrightnessRampRateSlowIncrease = mDisplayDeviceConfig.getBrightnessRampSlowIncrease();
+ mBrightnessRampDecreaseMaxTimeMillis =
+ mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis();
+ mBrightnessRampIncreaseMaxTimeMillis =
+ mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis();
+ }
+
+ private void loadNitsRange(Resources resources) {
+ if (mDisplayDeviceConfig != null && mDisplayDeviceConfig.getNits() != null) {
+ mNitsRange = mDisplayDeviceConfig.getNits();
+ } else {
+ Slog.w(mTag, "Screen brightness nits configuration is unavailable; falling back");
+ mNitsRange = BrightnessMappingStrategy.getFloatArray(resources
+ .obtainTypedArray(R.array.config_screenBrightnessNits));
+ }
+ }
+
+ private void reloadReduceBrightColours() {
+ if (mCdsi != null && mCdsi.isReduceBrightColorsActivated()) {
+ applyReduceBrightColorsSplineAdjustment();
+ }
+ }
+
+ @Override
+ public void setAutomaticScreenBrightnessMode(boolean isIdle) {
+ if (mAutomaticBrightnessController != null) {
+ if (isIdle) {
+ mAutomaticBrightnessController.switchToIdleMode();
+ } else {
+ mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
+ }
+ }
+ if (mDisplayWhiteBalanceController != null) {
+ mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
+ }
+ }
+
+ private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ sendUpdatePowerState();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ }
+ };
+
+ private final RampAnimator.Listener mRampAnimatorListener = new RampAnimator.Listener() {
+ @Override
+ public void onAnimationEnd() {
+ sendUpdatePowerState();
+ Message msg = mHandler.obtainMessage(MSG_BRIGHTNESS_RAMP_DONE);
+ mHandler.sendMessage(msg);
+ }
+ };
+
+ /** Clean up all resources that are accessed via the {@link #mHandler} thread. */
+ private void cleanupHandlerThreadAfterStop() {
+ setProximitySensorEnabled(false);
+ mHbmController.stop();
+ mBrightnessThrottler.stop();
+ mHandler.removeCallbacksAndMessages(null);
+
+ // Release any outstanding wakelocks we're still holding because of pending messages.
+ if (mUnfinishedBusiness) {
+ mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
+ mUnfinishedBusiness = false;
+ }
+ if (mOnStateChangedPending) {
+ mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
+ mOnStateChangedPending = false;
+ }
+ for (int i = 0; i < mOnProximityPositiveMessages; i++) {
+ mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
+ }
+ mOnProximityPositiveMessages = 0;
+ for (int i = 0; i < mOnProximityNegativeMessages; i++) {
+ mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
+ }
+ mOnProximityNegativeMessages = 0;
+
+ final float brightness = mPowerState != null
+ ? mPowerState.getScreenBrightness()
+ : PowerManager.BRIGHTNESS_MIN;
+ reportStats(brightness);
+
+ if (mPowerState != null) {
+ mPowerState.stop();
+ mPowerState = null;
+ }
+ }
+
+ private void updatePowerState() {
+ if (DEBUG) {
+ Trace.beginSection("DisplayPowerController#updatePowerState");
+ }
+ updatePowerStateInternal();
+ if (DEBUG) {
+ Trace.endSection();
+ }
+ }
+
+ private void updatePowerStateInternal() {
+ // Update the power state request.
+ final boolean mustNotify;
+ final int previousPolicy;
+ boolean mustInitialize = false;
+ int brightnessAdjustmentFlags = 0;
+ mBrightnessReasonTemp.set(null);
+ mTempBrightnessEvent.reset();
+ synchronized (mLock) {
+ if (mStopped) {
+ return;
+ }
+ mPendingUpdatePowerStateLocked = false;
+ if (mPendingRequestLocked == null) {
+ return; // wait until first actual power request
+ }
+
+ if (mPowerRequest == null) {
+ mPowerRequest = new DisplayPowerRequest(mPendingRequestLocked);
+ updatePendingProximityRequestsLocked();
+ mPendingRequestChangedLocked = false;
+ mustInitialize = true;
+ // Assume we're on and bright until told otherwise, since that's the state we turn
+ // on in.
+ previousPolicy = DisplayPowerRequest.POLICY_BRIGHT;
+ } else if (mPendingRequestChangedLocked) {
+ previousPolicy = mPowerRequest.policy;
+ mPowerRequest.copyFrom(mPendingRequestLocked);
+ updatePendingProximityRequestsLocked();
+ mPendingRequestChangedLocked = false;
+ mDisplayReadyLocked = false;
+ } else {
+ previousPolicy = mPowerRequest.policy;
+ }
+
+ mustNotify = !mDisplayReadyLocked;
+ }
+
+ // Compute the basic display state using the policy.
+ // We might override this below based on other factors.
+ // Initialise brightness as invalid.
+ int state;
+ float brightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ boolean performScreenOffTransition = false;
+ switch (mPowerRequest.policy) {
+ case DisplayPowerRequest.POLICY_OFF:
+ state = Display.STATE_OFF;
+ performScreenOffTransition = true;
+ break;
+ case DisplayPowerRequest.POLICY_DOZE:
+ if (mPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) {
+ state = mPowerRequest.dozeScreenState;
+ } else {
+ state = Display.STATE_DOZE;
+ }
+ if (!mAllowAutoBrightnessWhileDozingConfig) {
+ brightnessState = mPowerRequest.dozeScreenBrightness;
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE);
+ }
+ break;
+ case DisplayPowerRequest.POLICY_VR:
+ state = Display.STATE_VR;
+ break;
+ case DisplayPowerRequest.POLICY_DIM:
+ case DisplayPowerRequest.POLICY_BRIGHT:
+ default:
+ state = Display.STATE_ON;
+ break;
+ }
+ assert (state != Display.STATE_UNKNOWN);
+
+ boolean skipRampBecauseOfProximityChangeToNegative = false;
+ // Apply the proximity sensor.
+ if (mProximitySensor != null) {
+ if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) {
+ // At this point the policy says that the screen should be on, but we've been
+ // asked to listen to the prox sensor to adjust the display state, so lets make
+ // sure the sensor is on.
+ setProximitySensorEnabled(true);
+ if (!mScreenOffBecauseOfProximity
+ && mProximity == PROXIMITY_POSITIVE
+ && !mIgnoreProximityUntilChanged) {
+ // Prox sensor already reporting "near" so we should turn off the screen.
+ // Also checked that we aren't currently set to ignore the proximity sensor
+ // temporarily.
+ mScreenOffBecauseOfProximity = true;
+ sendOnProximityPositiveWithWakelock();
+ }
+ } else if (mWaitingForNegativeProximity
+ && mScreenOffBecauseOfProximity
+ && mProximity == PROXIMITY_POSITIVE
+ && state != Display.STATE_OFF) {
+ // The policy says that we should have the screen on, but it's off due to the prox
+ // and we've been asked to wait until the screen is far from the user to turn it
+ // back on. Let keep the prox sensor on so we can tell when it's far again.
+ setProximitySensorEnabled(true);
+ } else {
+ // We haven't been asked to use the prox sensor and we're not waiting on the screen
+ // to turn back on...so lets shut down the prox sensor.
+ setProximitySensorEnabled(false);
+ mWaitingForNegativeProximity = false;
+ }
+
+ if (mScreenOffBecauseOfProximity
+ && (mProximity != PROXIMITY_POSITIVE || mIgnoreProximityUntilChanged)) {
+ // The screen *was* off due to prox being near, but now it's "far" so lets turn
+ // the screen back on. Also turn it back on if we've been asked to ignore the
+ // prox sensor temporarily.
+ mScreenOffBecauseOfProximity = false;
+ skipRampBecauseOfProximityChangeToNegative = true;
+ sendOnProximityNegativeWithWakelock();
+ }
+ } else {
+ mWaitingForNegativeProximity = false;
+ mIgnoreProximityUntilChanged = false;
+ }
+
+ if (!mLogicalDisplay.isEnabled()
+ || mLogicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION
+ || mScreenOffBecauseOfProximity) {
+ state = Display.STATE_OFF;
+ }
+
+ // Initialize things the first time the power state is changed.
+ if (mustInitialize) {
+ initialize(state);
+ }
+
+ // Animate the screen state change unless already animating.
+ // The transition may be deferred, so after this point we will use the
+ // actual state instead of the desired one.
+ final int oldState = mPowerState.getScreenState();
+ animateScreenStateChange(state, performScreenOffTransition);
+ state = mPowerState.getScreenState();
+
+ if (state == Display.STATE_OFF) {
+ brightnessState = PowerManager.BRIGHTNESS_OFF_FLOAT;
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_SCREEN_OFF);
+ }
+
+ // Always use the VR brightness when in the VR state.
+ if (state == Display.STATE_VR) {
+ brightnessState = mScreenBrightnessForVr;
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_VR);
+ }
+
+ if ((Float.isNaN(brightnessState))
+ && isValidBrightnessValue(mPowerRequest.screenBrightnessOverride)) {
+ brightnessState = mPowerRequest.screenBrightnessOverride;
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_OVERRIDE);
+ mAppliedScreenBrightnessOverride = true;
+ } else {
+ mAppliedScreenBrightnessOverride = false;
+ }
+
+ final boolean autoBrightnessEnabledInDoze =
+ mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
+ final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness
+ && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
+ && Float.isNaN(brightnessState)
+ && mAutomaticBrightnessController != null;
+ final boolean autoBrightnessDisabledDueToDisplayOff = mPowerRequest.useAutoBrightness
+ && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
+ final int autoBrightnessState = autoBrightnessEnabled
+ ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
+ : autoBrightnessDisabledDueToDisplayOff
+ ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
+ : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
+
+ final boolean userSetBrightnessChanged = updateUserSetScreenBrightness();
+
+ // Use the temporary screen brightness if there isn't an override, either from
+ // WindowManager or based on the display state.
+ if (isValidBrightnessValue(mTemporaryScreenBrightness)) {
+ brightnessState = mTemporaryScreenBrightness;
+ mAppliedTemporaryBrightness = true;
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_TEMPORARY);
+ } else {
+ mAppliedTemporaryBrightness = false;
+ }
+
+ final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment();
+
+ // Use the autobrightness adjustment override if set.
+ final float autoBrightnessAdjustment;
+ if (!Float.isNaN(mTemporaryAutoBrightnessAdjustment)) {
+ autoBrightnessAdjustment = mTemporaryAutoBrightnessAdjustment;
+ brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO_TEMP;
+ mAppliedTemporaryAutoBrightnessAdjustment = true;
+ } else {
+ autoBrightnessAdjustment = mAutoBrightnessAdjustment;
+ brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO;
+ mAppliedTemporaryAutoBrightnessAdjustment = false;
+ }
+ // Apply brightness boost.
+ // We do this here after deciding whether auto-brightness is enabled so that we don't
+ // disable the light sensor during this temporary state. That way when boost ends we will
+ // be able to resume normal auto-brightness behavior without any delay.
+ if (mPowerRequest.boostScreenBrightness
+ && brightnessState != PowerManager.BRIGHTNESS_OFF_FLOAT) {
+ brightnessState = PowerManager.BRIGHTNESS_MAX;
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_BOOST);
+ mAppliedBrightnessBoost = true;
+ } else {
+ mAppliedBrightnessBoost = false;
+ }
+
+ // If the brightness is already set then it's been overridden by something other than the
+ // user, or is a temporary adjustment.
+ boolean userInitiatedChange = (Float.isNaN(brightnessState))
+ && (autoBrightnessAdjustmentChanged || userSetBrightnessChanged);
+ boolean hadUserBrightnessPoint = false;
+ // Configure auto-brightness.
+ if (mAutomaticBrightnessController != null) {
+ hadUserBrightnessPoint = mAutomaticBrightnessController.hasUserDataPoints();
+ mAutomaticBrightnessController.configure(autoBrightnessState,
+ mBrightnessConfiguration,
+ mLastUserSetScreenBrightness,
+ userSetBrightnessChanged, autoBrightnessAdjustment,
+ autoBrightnessAdjustmentChanged, mPowerRequest.policy);
+ }
+
+ if (mBrightnessTracker != null) {
+ mBrightnessTracker.setBrightnessConfiguration(mBrightnessConfiguration);
+ }
+
+ boolean updateScreenBrightnessSetting = false;
+
+ // Apply auto-brightness.
+ boolean slowChange = false;
+ if (Float.isNaN(brightnessState)) {
+ float newAutoBrightnessAdjustment = autoBrightnessAdjustment;
+ if (autoBrightnessEnabled) {
+ brightnessState = mAutomaticBrightnessController.getAutomaticScreenBrightness(
+ mTempBrightnessEvent);
+ newAutoBrightnessAdjustment =
+ mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment();
+ }
+ if (isValidBrightnessValue(brightnessState)
+ || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
+ // Use current auto-brightness value and slowly adjust to changes.
+ brightnessState = clampScreenBrightness(brightnessState);
+ if (mAppliedAutoBrightness && !autoBrightnessAdjustmentChanged) {
+ slowChange = true; // slowly adapt to auto-brightness
+ }
+ updateScreenBrightnessSetting = mCurrentScreenBrightnessSetting != brightnessState;
+ mAppliedAutoBrightness = true;
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
+ } else {
+ mAppliedAutoBrightness = false;
+ }
+ if (autoBrightnessAdjustment != newAutoBrightnessAdjustment) {
+ // If the autobrightness controller has decided to change the adjustment value
+ // used, make sure that's reflected in settings.
+ putAutoBrightnessAdjustmentSetting(newAutoBrightnessAdjustment);
+ } else {
+ // Adjustment values resulted in no change
+ brightnessAdjustmentFlags = 0;
+ }
+ } else {
+ // Any non-auto-brightness values such as override or temporary should still be subject
+ // to clamping so that they don't go beyond the current max as specified by HBM
+ // Controller.
+ brightnessState = clampScreenBrightness(brightnessState);
+ mAppliedAutoBrightness = false;
+ brightnessAdjustmentFlags = 0;
+ }
+
+ // Use default brightness when dozing unless overridden.
+ if ((Float.isNaN(brightnessState))
+ && Display.isDozeState(state)) {
+ brightnessState = clampScreenBrightness(mScreenBrightnessDozeConfig);
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
+ }
+
+ // Apply manual brightness.
+ if (Float.isNaN(brightnessState)) {
+ brightnessState = clampScreenBrightness(mCurrentScreenBrightnessSetting);
+ if (brightnessState != mCurrentScreenBrightnessSetting) {
+ // The manually chosen screen brightness is outside of the currently allowed
+ // range (i.e., high-brightness-mode), make sure we tell the rest of the system
+ // by updating the setting.
+ updateScreenBrightnessSetting = true;
+ }
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
+ }
+
+ // Now that a desired brightness has been calculated, apply brightness throttling. The
+ // dimming and low power transformations that follow can only dim brightness further.
+ //
+ // We didn't do this earlier through brightness clamping because we need to know both
+ // unthrottled (unclamped/ideal) and throttled brightness levels for subsequent operations.
+ // Note throttling effectively changes the allowed brightness range, so, similarly to HBM,
+ // we broadcast this change through setting.
+ final float unthrottledBrightnessState = brightnessState;
+ if (mBrightnessThrottler.isThrottled()) {
+ mTempBrightnessEvent.setThermalMax(mBrightnessThrottler.getBrightnessCap());
+ brightnessState = Math.min(brightnessState, mBrightnessThrottler.getBrightnessCap());
+ mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_THROTTLED);
+ if (!mAppliedThrottling) {
+ // Brightness throttling is needed, so do so quickly.
+ // Later, when throttling is removed, we let other mechanisms decide on speed.
+ slowChange = false;
+ }
+ mAppliedThrottling = true;
+ } else if (mAppliedThrottling) {
+ mAppliedThrottling = false;
+ }
+
+ if (updateScreenBrightnessSetting) {
+ // Tell the rest of the system about the new brightness in case we had to change it
+ // for things like auto-brightness or high-brightness-mode. Note that we do this
+ // before applying the low power or dim transformations so that the slider
+ // accurately represents the full possible range, even if they range changes what
+ // it means in absolute terms.
+ updateScreenBrightnessSetting(brightnessState);
+ }
+
+ // Apply dimming by at least some minimum amount when user activity
+ // timeout is about to expire.
+ if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
+ if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
+ brightnessState = Math.max(
+ Math.min(brightnessState - mScreenBrightnessMinimumDimAmount,
+ mScreenBrightnessDimConfig),
+ PowerManager.BRIGHTNESS_MIN);
+ mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_DIMMED);
+ }
+ if (!mAppliedDimming) {
+ slowChange = false;
+ }
+ mAppliedDimming = true;
+ } else if (mAppliedDimming) {
+ slowChange = false;
+ mAppliedDimming = false;
+ }
+ // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor
+ // as long as it is above the minimum threshold.
+ if (mPowerRequest.lowPowerMode) {
+ if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
+ final float brightnessFactor =
+ Math.min(mPowerRequest.screenLowPowerBrightnessFactor, 1);
+ final float lowPowerBrightnessFloat = (brightnessState * brightnessFactor);
+ brightnessState = Math.max(lowPowerBrightnessFloat, PowerManager.BRIGHTNESS_MIN);
+ mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_LOW_POWER);
+ }
+ if (!mAppliedLowPower) {
+ slowChange = false;
+ }
+ mAppliedLowPower = true;
+ } else if (mAppliedLowPower) {
+ slowChange = false;
+ mAppliedLowPower = false;
+ }
+
+ // The current brightness to use has been calculated at this point, and HbmController should
+ // be notified so that it can accurately calculate HDR or HBM levels. We specifically do it
+ // here instead of having HbmController listen to the brightness setting because certain
+ // brightness sources (such as an app override) are not saved to the setting, but should be
+ // reflected in HBM calculations.
+ mHbmController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
+ mBrightnessThrottler.getBrightnessMaxReason());
+
+ // Animate the screen brightness when the screen is on or dozing.
+ // Skip the animation when the screen is off or suspended or transition to/from VR.
+ boolean brightnessAdjusted = false;
+ final boolean brightnessIsTemporary =
+ mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment;
+ if (!mPendingScreenOff) {
+ if (mSkipScreenOnBrightnessRamp) {
+ if (state == Display.STATE_ON) {
+ if (mSkipRampState == RAMP_STATE_SKIP_NONE && mDozing) {
+ mInitialAutoBrightness = brightnessState;
+ mSkipRampState = RAMP_STATE_SKIP_INITIAL;
+ } else if (mSkipRampState == RAMP_STATE_SKIP_INITIAL
+ && mUseSoftwareAutoBrightnessConfig
+ && !BrightnessSynchronizer.floatEquals(brightnessState,
+ mInitialAutoBrightness)) {
+ mSkipRampState = RAMP_STATE_SKIP_AUTOBRIGHT;
+ } else if (mSkipRampState == RAMP_STATE_SKIP_AUTOBRIGHT) {
+ mSkipRampState = RAMP_STATE_SKIP_NONE;
+ }
+ } else {
+ mSkipRampState = RAMP_STATE_SKIP_NONE;
+ }
+ }
+
+ final boolean wasOrWillBeInVr =
+ (state == Display.STATE_VR || oldState == Display.STATE_VR);
+ final boolean initialRampSkip = (state == Display.STATE_ON && mSkipRampState
+ != RAMP_STATE_SKIP_NONE) || skipRampBecauseOfProximityChangeToNegative;
+ // While dozing, sometimes the brightness is split into buckets. Rather than animating
+ // through the buckets, which is unlikely to be smooth in the first place, just jump
+ // right to the suggested brightness.
+ final boolean hasBrightnessBuckets =
+ Display.isDozeState(state) && mBrightnessBucketsInDozeConfig;
+ // If the color fade is totally covering the screen then we can change the backlight
+ // level without it being a noticeable jump since any actual content isn't yet visible.
+ final boolean isDisplayContentVisible =
+ mColorFadeEnabled && mPowerState.getColorFadeLevel() == 1.0f;
+ // We only want to animate the brightness if it is between 0.0f and 1.0f.
+ // brightnessState can contain the values -1.0f and NaN, which we do not want to
+ // animate to. To avoid this, we check the value first.
+ // If the brightnessState is off (-1.0f) we still want to animate to the minimum
+ // brightness (0.0f) to accommodate for LED displays, which can appear bright to the
+ // user even when the display is all black. We also clamp here in case some
+ // transformations to the brightness have pushed it outside of the currently
+ // allowed range.
+ float animateValue = clampScreenBrightness(brightnessState);
+
+ // If there are any HDR layers on the screen, we have a special brightness value that we
+ // use instead. We still preserve the calculated brightness for Standard Dynamic Range
+ // (SDR) layers, but the main brightness value will be the one for HDR.
+ float sdrAnimateValue = animateValue;
+ // TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be
+ // done in HighBrightnessModeController.
+ if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
+ && ((mBrightnessReason.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0
+ || (mBrightnessReason.getModifier() & BrightnessReason.MODIFIER_LOW_POWER)
+ == 0)) {
+ // We want to scale HDR brightness level with the SDR level
+ animateValue = mHbmController.getHdrBrightnessValue();
+ }
+
+ final float currentBrightness = mPowerState.getScreenBrightness();
+ final float currentSdrBrightness = mPowerState.getSdrScreenBrightness();
+ if (isValidBrightnessValue(animateValue)
+ && (animateValue != currentBrightness
+ || sdrAnimateValue != currentSdrBrightness)) {
+ if (initialRampSkip || hasBrightnessBuckets
+ || wasOrWillBeInVr || !isDisplayContentVisible || brightnessIsTemporary) {
+ animateScreenBrightness(animateValue, sdrAnimateValue,
+ SCREEN_ANIMATION_RATE_MINIMUM);
+ } else {
+ boolean isIncreasing = animateValue > currentBrightness;
+ final float rampSpeed;
+ if (isIncreasing && slowChange) {
+ rampSpeed = mBrightnessRampRateSlowIncrease;
+ } else if (isIncreasing && !slowChange) {
+ rampSpeed = mBrightnessRampRateFastIncrease;
+ } else if (!isIncreasing && slowChange) {
+ rampSpeed = mBrightnessRampRateSlowDecrease;
+ } else {
+ rampSpeed = mBrightnessRampRateFastDecrease;
+ }
+ animateScreenBrightness(animateValue, sdrAnimateValue, rampSpeed);
+ }
+ }
+
+ // Report brightness to brightnesstracker:
+ // If brightness is not temporary (ie the slider has been released)
+ // AND if we are not in idle screen brightness mode.
+ if (!brightnessIsTemporary
+ && (mAutomaticBrightnessController != null
+ && !mAutomaticBrightnessController.isInIdleMode())) {
+ if (userInitiatedChange && (mAutomaticBrightnessController == null
+ || !mAutomaticBrightnessController.hasValidAmbientLux())) {
+ // If we don't have a valid lux reading we can't report a valid
+ // slider event so notify as if the system changed the brightness.
+ userInitiatedChange = false;
+ }
+ notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
+ hadUserBrightnessPoint);
+ }
+
+ // We save the brightness info *after* the brightness setting has been changed and
+ // adjustments made so that the brightness info reflects the latest value.
+ brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(), animateValue);
+ } else {
+ brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting());
+ }
+
+ // Only notify if the brightness adjustment is not temporary (i.e. slider has been released)
+ if (brightnessAdjusted && !brightnessIsTemporary) {
+ postBrightnessChangeRunnable();
+ }
+
+ // Log any changes to what is currently driving the brightness setting.
+ if (!mBrightnessReasonTemp.equals(mBrightnessReason) || brightnessAdjustmentFlags != 0) {
+ Slog.v(mTag, "Brightness [" + brightnessState + "] reason changing to: '"
+ + mBrightnessReasonTemp.toString(brightnessAdjustmentFlags)
+ + "', previous reason: '" + mBrightnessReason + "'.");
+ mBrightnessReason.set(mBrightnessReasonTemp);
+ } else if (mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_MANUAL
+ && userSetBrightnessChanged) {
+ Slog.v(mTag, "Brightness [" + brightnessState + "] manual adjustment.");
+ }
+
+
+ // Log brightness events when a detail of significance has changed. Generally this is the
+ // brightness itself changing, but also includes data like HBM cap, thermal throttling
+ // brightness cap, RBC state, etc.
+ mTempBrightnessEvent.setTime(System.currentTimeMillis());
+ mTempBrightnessEvent.setBrightness(brightnessState);
+ mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
+ mTempBrightnessEvent.setReason(mBrightnessReason);
+ mTempBrightnessEvent.setHbmMax(mHbmController.getCurrentBrightnessMax());
+ mTempBrightnessEvent.setHbmMode(mHbmController.getHighBrightnessMode());
+ mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
+ | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0)
+ | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0));
+ mTempBrightnessEvent.setRbcStrength(mCdsi != null
+ ? mCdsi.getReduceBrightColorsStrength() : -1);
+ mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
+ // Temporary is what we use during slider interactions. We avoid logging those so that
+ // we don't spam logcat when the slider is being used.
+ boolean tempToTempTransition =
+ mTempBrightnessEvent.getReason().getReason() == BrightnessReason.REASON_TEMPORARY
+ && mLastBrightnessEvent.getReason().getReason()
+ == BrightnessReason.REASON_TEMPORARY;
+ if ((!mTempBrightnessEvent.equalsMainData(mLastBrightnessEvent) && !tempToTempTransition)
+ || brightnessAdjustmentFlags != 0) {
+ float lastBrightness = mLastBrightnessEvent.getBrightness();
+ mTempBrightnessEvent.setInitialBrightness(lastBrightness);
+ mTempBrightnessEvent.setFastAmbientLux(
+ mAutomaticBrightnessController == null
+ ? -1f : mAutomaticBrightnessController.getFastAmbientLux());
+ mTempBrightnessEvent.setSlowAmbientLux(
+ mAutomaticBrightnessController == null
+ ? -1f : mAutomaticBrightnessController.getSlowAmbientLux());
+ mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
+ mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
+ BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
+ // Adjustment flags (and user-set flag) only get added after the equality checks since
+ // they are transient.
+ newEvent.setAdjustmentFlags(brightnessAdjustmentFlags);
+ newEvent.setFlags(newEvent.getFlags() | (userSetBrightnessChanged
+ ? BrightnessEvent.FLAG_USER_SET : 0));
+ Slog.i(mTag, newEvent.toString(/* includeTime= */ false));
+
+ if (userSetBrightnessChanged) {
+ logManualBrightnessEvent(newEvent);
+ }
+ if (mBrightnessEventRingBuffer != null) {
+ mBrightnessEventRingBuffer.append(newEvent);
+ }
+ }
+
+ // Update display white-balance.
+ if (mDisplayWhiteBalanceController != null) {
+ if (state == Display.STATE_ON && mDisplayWhiteBalanceSettings.isEnabled()) {
+ mDisplayWhiteBalanceController.setEnabled(true);
+ mDisplayWhiteBalanceController.updateDisplayColorTemperature();
+ } else {
+ mDisplayWhiteBalanceController.setEnabled(false);
+ }
+ }
+
+ // Determine whether the display is ready for use in the newly requested state.
+ // Note that we do not wait for the brightness ramp animation to complete before
+ // reporting the display is ready because we only need to ensure the screen is in the
+ // right power state even as it continues to converge on the desired brightness.
+ final boolean ready = mPendingScreenOnUnblocker == null
+ && (!mColorFadeEnabled || (!mColorFadeOnAnimator.isStarted()
+ && !mColorFadeOffAnimator.isStarted()))
+ && mPowerState.waitUntilClean(mCleanListener);
+ final boolean finished = ready
+ && !mScreenBrightnessRampAnimator.isAnimating();
+
+ // Notify policy about screen turned on.
+ if (ready && state != Display.STATE_OFF
+ && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_ON) {
+ setReportedScreenState(REPORTED_TO_POLICY_SCREEN_ON);
+ mWindowManagerPolicy.screenTurnedOn(mDisplayId);
+ }
+
+ // Grab a wake lock if we have unfinished business.
+ if (!finished && !mUnfinishedBusiness) {
+ if (DEBUG) {
+ Slog.d(mTag, "Unfinished business...");
+ }
+ mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
+ mUnfinishedBusiness = true;
+ }
+
+ // Notify the power manager when ready.
+ if (ready && mustNotify) {
+ // Send state change.
+ synchronized (mLock) {
+ if (!mPendingRequestChangedLocked) {
+ mDisplayReadyLocked = true;
+
+ if (DEBUG) {
+ Slog.d(mTag, "Display ready!");
+ }
+ }
+ }
+ sendOnStateChangedWithWakelock();
+ }
+
+ // Release the wake lock when we have no unfinished business.
+ if (finished && mUnfinishedBusiness) {
+ if (DEBUG) {
+ Slog.d(mTag, "Finished business...");
+ }
+ mUnfinishedBusiness = false;
+ mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
+ }
+
+ // Record if dozing for future comparison.
+ mDozing = state != Display.STATE_ON;
+
+ if (previousPolicy != mPowerRequest.policy) {
+ logDisplayPolicyChanged(mPowerRequest.policy);
+ }
+ }
+
+ @Override
+ public void updateBrightness() {
+ sendUpdatePowerState();
+ }
+
+ /**
+ * Ignores the proximity sensor until the sensor state changes, but only if the sensor is
+ * currently enabled and forcing the screen to be dark.
+ */
+ @Override
+ public void ignoreProximitySensorUntilChanged() {
+ mHandler.sendEmptyMessage(MSG_IGNORE_PROXIMITY);
+ }
+
+ @Override
+ public void setBrightnessConfiguration(BrightnessConfiguration c) {
+ Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS, c);
+ msg.sendToTarget();
+ }
+
+ @Override
+ public void setTemporaryBrightness(float brightness) {
+ Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_BRIGHTNESS,
+ Float.floatToIntBits(brightness), 0 /*unused*/);
+ msg.sendToTarget();
+ }
+
+ @Override
+ public void setTemporaryAutoBrightnessAdjustment(float adjustment) {
+ Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT,
+ Float.floatToIntBits(adjustment), 0 /*unused*/);
+ msg.sendToTarget();
+ }
+
+ @Override
+ public BrightnessInfo getBrightnessInfo() {
+ synchronized (mCachedBrightnessInfo) {
+ return new BrightnessInfo(
+ mCachedBrightnessInfo.brightness.value,
+ mCachedBrightnessInfo.adjustedBrightness.value,
+ mCachedBrightnessInfo.brightnessMin.value,
+ mCachedBrightnessInfo.brightnessMax.value,
+ mCachedBrightnessInfo.hbmMode.value,
+ mCachedBrightnessInfo.hbmTransitionPoint.value,
+ mCachedBrightnessInfo.brightnessMaxReason.value);
+ }
+ }
+
+ private boolean saveBrightnessInfo(float brightness) {
+ return saveBrightnessInfo(brightness, brightness);
+ }
+
+ private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) {
+ synchronized (mCachedBrightnessInfo) {
+ final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(),
+ mBrightnessThrottler.getBrightnessCap());
+ final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(),
+ mBrightnessThrottler.getBrightnessCap());
+ boolean changed = false;
+
+ changed |=
+ mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightness,
+ brightness);
+ changed |=
+ mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.adjustedBrightness,
+ adjustedBrightness);
+ changed |=
+ mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMin,
+ minBrightness);
+ changed |=
+ mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMax,
+ maxBrightness);
+ changed |=
+ mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode,
+ mHbmController.getHighBrightnessMode());
+ changed |=
+ mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint,
+ mHbmController.getTransitionPoint());
+ changed |=
+ mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
+ mBrightnessThrottler.getBrightnessMaxReason());
+
+ return changed;
+ }
+ }
+
+ void postBrightnessChangeRunnable() {
+ mHandler.post(mOnBrightnessChangeRunnable);
+ }
+
+ private HighBrightnessModeController createHbmControllerLocked() {
+ final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+ final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
+ final IBinder displayToken =
+ mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayTokenLocked();
+ final String displayUniqueId =
+ mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
+ final DisplayDeviceConfig.HighBrightnessModeData hbmData =
+ ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
+ final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+ return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken,
+ displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
+ new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
+ @Override
+ public float getHdrBrightnessFromSdr(float sdrBrightness) {
+ return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness);
+ }
+ },
+ () -> {
+ sendUpdatePowerState();
+ postBrightnessChangeRunnable();
+ // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.update();
+ }
+ }, mContext);
+ }
+
+ private BrightnessThrottler createBrightnessThrottlerLocked() {
+ final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+ final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
+ final DisplayDeviceConfig.BrightnessThrottlingData data =
+ ddConfig != null ? ddConfig.getBrightnessThrottlingData() : null;
+ return new BrightnessThrottler(mHandler, data,
+ () -> {
+ sendUpdatePowerState();
+ postBrightnessChangeRunnable();
+ }, mUniqueDisplayId);
+ }
+
+ private void blockScreenOn() {
+ if (mPendingScreenOnUnblocker == null) {
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
+ mPendingScreenOnUnblocker = new ScreenOnUnblocker();
+ mScreenOnBlockStartRealTime = SystemClock.elapsedRealtime();
+ Slog.i(mTag, "Blocking screen on until initial contents have been drawn.");
+ }
+ }
+
+ private void unblockScreenOn() {
+ if (mPendingScreenOnUnblocker != null) {
+ mPendingScreenOnUnblocker = null;
+ long delay = SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime;
+ Slog.i(mTag, "Unblocked screen on after " + delay + " ms");
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
+ }
+ }
+
+ private void blockScreenOff() {
+ if (mPendingScreenOffUnblocker == null) {
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_OFF_BLOCKED_TRACE_NAME, 0);
+ mPendingScreenOffUnblocker = new ScreenOffUnblocker();
+ mScreenOffBlockStartRealTime = SystemClock.elapsedRealtime();
+ Slog.i(mTag, "Blocking screen off");
+ }
+ }
+
+ private void unblockScreenOff() {
+ if (mPendingScreenOffUnblocker != null) {
+ mPendingScreenOffUnblocker = null;
+ long delay = SystemClock.elapsedRealtime() - mScreenOffBlockStartRealTime;
+ Slog.i(mTag, "Unblocked screen off after " + delay + " ms");
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_OFF_BLOCKED_TRACE_NAME, 0);
+ }
+ }
+
+ private boolean setScreenState(int state) {
+ return setScreenState(state, false /*reportOnly*/);
+ }
+
+ private boolean setScreenState(int state, boolean reportOnly) {
+ final boolean isOff = (state == Display.STATE_OFF);
+
+ if (mPowerState.getScreenState() != state
+ || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
+ // If we are trying to turn screen off, give policy a chance to do something before we
+ // actually turn the screen off.
+ if (isOff && !mScreenOffBecauseOfProximity) {
+ if (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_ON
+ || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
+ setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_OFF);
+ blockScreenOff();
+ mWindowManagerPolicy.screenTurningOff(mDisplayId, mPendingScreenOffUnblocker);
+ unblockScreenOff();
+ } else if (mPendingScreenOffUnblocker != null) {
+ // Abort doing the state change until screen off is unblocked.
+ return false;
+ }
+ }
+
+ if (!reportOnly && mPowerState.getScreenState() != state) {
+ Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
+ // TODO(b/153319140) remove when we can get this from the above trace invocation
+ SystemProperties.set("debug.tracing.screen_state", String.valueOf(state));
+ mPowerState.setScreenState(state);
+ // Tell battery stats about the transition.
+ noteScreenState(state);
+ }
+ }
+
+ // Tell the window manager policy when the screen is turned off or on unless it's due
+ // to the proximity sensor. We temporarily block turning the screen on until the
+ // window manager is ready by leaving a black surface covering the screen.
+ // This surface is essentially the final state of the color fade animation and
+ // it is only removed once the window manager tells us that the activity has
+ // finished drawing underneath.
+ if (isOff && mReportedScreenStateToPolicy != REPORTED_TO_POLICY_SCREEN_OFF
+ && !mScreenOffBecauseOfProximity) {
+ setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF);
+ unblockScreenOn();
+ mWindowManagerPolicy.screenTurnedOff(mDisplayId);
+ } else if (!isOff
+ && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_OFF) {
+
+ // We told policy already that screen was turning off, but now we changed our minds.
+ // Complete the full state transition on -> turningOff -> off.
+ unblockScreenOff();
+ mWindowManagerPolicy.screenTurnedOff(mDisplayId);
+ setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF);
+ }
+ if (!isOff
+ && (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_OFF
+ || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED)) {
+ setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_ON);
+ if (mPowerState.getColorFadeLevel() == 0.0f) {
+ blockScreenOn();
+ } else {
+ unblockScreenOn();
+ }
+ mWindowManagerPolicy.screenTurningOn(mDisplayId, mPendingScreenOnUnblocker);
+ }
+
+ // Return true if the screen isn't blocked.
+ return mPendingScreenOnUnblocker == null;
+ }
+
+ private void setReportedScreenState(int state) {
+ Trace.traceCounter(Trace.TRACE_TAG_POWER, "ReportedScreenStateToPolicy", state);
+ mReportedScreenStateToPolicy = state;
+ }
+
+ private void loadAmbientLightSensor() {
+ DisplayDeviceConfig.SensorData lightSensor = mDisplayDeviceConfig.getAmbientLightSensor();
+ final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
+ ? Sensor.TYPE_LIGHT : SensorUtils.NO_FALLBACK;
+ mLightSensor = SensorUtils.findSensor(mSensorManager, lightSensor.type, lightSensor.name,
+ fallbackType);
+ }
+
+ private void loadProximitySensor() {
+ if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
+ return;
+ }
+ final DisplayDeviceConfig.SensorData proxSensor =
+ mDisplayDeviceConfig.getProximitySensor();
+ final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
+ ? Sensor.TYPE_PROXIMITY : SensorUtils.NO_FALLBACK;
+ mProximitySensor = SensorUtils.findSensor(mSensorManager, proxSensor.type, proxSensor.name,
+ fallbackType);
+ if (mProximitySensor != null) {
+ mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
+ TYPICAL_PROXIMITY_THRESHOLD);
+ }
+ }
+
+ private float clampScreenBrightnessForVr(float value) {
+ return MathUtils.constrain(
+ value, mScreenBrightnessForVrRangeMinimum,
+ mScreenBrightnessForVrRangeMaximum);
+ }
+
+ private float clampScreenBrightness(float value) {
+ if (Float.isNaN(value)) {
+ value = PowerManager.BRIGHTNESS_MIN;
+ }
+ return MathUtils.constrain(value,
+ mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax());
+ }
+
+ // Checks whether the brightness is within the valid brightness range, not including off.
+ private boolean isValidBrightnessValue(float brightness) {
+ return brightness >= PowerManager.BRIGHTNESS_MIN
+ && brightness <= PowerManager.BRIGHTNESS_MAX;
+ }
+
+ private void animateScreenBrightness(float target, float sdrTarget, float rate) {
+ if (DEBUG) {
+ Slog.d(mTag, "Animating brightness: target=" + target + ", sdrTarget=" + sdrTarget
+ + ", rate=" + rate);
+ }
+ if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate)) {
+ Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target);
+ // TODO(b/153319140) remove when we can get this from the above trace invocation
+ SystemProperties.set("debug.tracing.screen_brightness", String.valueOf(target));
+ noteScreenBrightness(target);
+ }
+ }
+
+ private void animateScreenStateChange(int target, boolean performScreenOffTransition) {
+ // If there is already an animation in progress, don't interfere with it.
+ if (mColorFadeEnabled
+ && (mColorFadeOnAnimator.isStarted() || mColorFadeOffAnimator.isStarted())) {
+ if (target != Display.STATE_ON) {
+ return;
+ }
+ // If display state changed to on, proceed and stop the color fade and turn screen on.
+ mPendingScreenOff = false;
+ }
+
+ if (mDisplayBlanksAfterDozeConfig
+ && Display.isDozeState(mPowerState.getScreenState())
+ && !Display.isDozeState(target)) {
+ // Skip the screen off animation and add a black surface to hide the
+ // contents of the screen.
+ mPowerState.prepareColorFade(mContext,
+ mColorFadeFadesConfig ? ColorFade.MODE_FADE : ColorFade.MODE_WARM_UP);
+ if (mColorFadeOffAnimator != null) {
+ mColorFadeOffAnimator.end();
+ }
+ // Some display hardware will blank itself on the transition between doze and non-doze
+ // but still on display states. In this case we want to report to policy that the
+ // display has turned off so it can prepare the appropriate power on animation, but we
+ // don't want to actually transition to the fully off state since that takes
+ // significantly longer to transition from.
+ setScreenState(Display.STATE_OFF, target != Display.STATE_OFF /*reportOnly*/);
+ }
+
+ // If we were in the process of turning off the screen but didn't quite
+ // finish. Then finish up now to prevent a jarring transition back
+ // to screen on if we skipped blocking screen on as usual.
+ if (mPendingScreenOff && target != Display.STATE_OFF) {
+ setScreenState(Display.STATE_OFF);
+ mPendingScreenOff = false;
+ mPowerState.dismissColorFadeResources();
+ }
+
+ if (target == Display.STATE_ON) {
+ // Want screen on. The contents of the screen may not yet
+ // be visible if the color fade has not been dismissed because
+ // its last frame of animation is solid black.
+ if (!setScreenState(Display.STATE_ON)) {
+ return; // screen on blocked
+ }
+ if (USE_COLOR_FADE_ON_ANIMATION && mColorFadeEnabled && mPowerRequest.isBrightOrDim()) {
+ // Perform screen on animation.
+ if (mPowerState.getColorFadeLevel() == 1.0f) {
+ mPowerState.dismissColorFade();
+ } else if (mPowerState.prepareColorFade(mContext,
+ mColorFadeFadesConfig
+ ? ColorFade.MODE_FADE : ColorFade.MODE_WARM_UP)) {
+ mColorFadeOnAnimator.start();
+ } else {
+ mColorFadeOnAnimator.end();
+ }
+ } else {
+ // Skip screen on animation.
+ mPowerState.setColorFadeLevel(1.0f);
+ mPowerState.dismissColorFade();
+ }
+ } else if (target == Display.STATE_VR) {
+ // Wait for brightness animation to complete beforehand when entering VR
+ // from screen on to prevent a perceptible jump because brightness may operate
+ // differently when the display is configured for dozing.
+ if (mScreenBrightnessRampAnimator.isAnimating()
+ && mPowerState.getScreenState() == Display.STATE_ON) {
+ return;
+ }
+
+ // Set screen state.
+ if (!setScreenState(Display.STATE_VR)) {
+ return; // screen on blocked
+ }
+
+ // Dismiss the black surface without fanfare.
+ mPowerState.setColorFadeLevel(1.0f);
+ mPowerState.dismissColorFade();
+ } else if (target == Display.STATE_DOZE) {
+ // Want screen dozing.
+ // Wait for brightness animation to complete beforehand when entering doze
+ // from screen on to prevent a perceptible jump because brightness may operate
+ // differently when the display is configured for dozing.
+ if (mScreenBrightnessRampAnimator.isAnimating()
+ && mPowerState.getScreenState() == Display.STATE_ON) {
+ return;
+ }
+
+ // Set screen state.
+ if (!setScreenState(Display.STATE_DOZE)) {
+ return; // screen on blocked
+ }
+
+ // Dismiss the black surface without fanfare.
+ mPowerState.setColorFadeLevel(1.0f);
+ mPowerState.dismissColorFade();
+ } else if (target == Display.STATE_DOZE_SUSPEND) {
+ // Want screen dozing and suspended.
+ // Wait for brightness animation to complete beforehand unless already
+ // suspended because we may not be able to change it after suspension.
+ if (mScreenBrightnessRampAnimator.isAnimating()
+ && mPowerState.getScreenState() != Display.STATE_DOZE_SUSPEND) {
+ return;
+ }
+
+ // If not already suspending, temporarily set the state to doze until the
+ // screen on is unblocked, then suspend.
+ if (mPowerState.getScreenState() != Display.STATE_DOZE_SUSPEND) {
+ if (!setScreenState(Display.STATE_DOZE)) {
+ return; // screen on blocked
+ }
+ setScreenState(Display.STATE_DOZE_SUSPEND); // already on so can't block
+ }
+
+ // Dismiss the black surface without fanfare.
+ mPowerState.setColorFadeLevel(1.0f);
+ mPowerState.dismissColorFade();
+ } else if (target == Display.STATE_ON_SUSPEND) {
+ // Want screen full-power and suspended.
+ // Wait for brightness animation to complete beforehand unless already
+ // suspended because we may not be able to change it after suspension.
+ if (mScreenBrightnessRampAnimator.isAnimating()
+ && mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
+ return;
+ }
+
+ // If not already suspending, temporarily set the state to on until the
+ // screen on is unblocked, then suspend.
+ if (mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
+ if (!setScreenState(Display.STATE_ON)) {
+ return;
+ }
+ setScreenState(Display.STATE_ON_SUSPEND);
+ }
+
+ // Dismiss the black surface without fanfare.
+ mPowerState.setColorFadeLevel(1.0f);
+ mPowerState.dismissColorFade();
+ } else {
+ // Want screen off.
+ mPendingScreenOff = true;
+ if (!mColorFadeEnabled) {
+ mPowerState.setColorFadeLevel(0.0f);
+ }
+
+ if (mPowerState.getColorFadeLevel() == 0.0f) {
+ // Turn the screen off.
+ // A black surface is already hiding the contents of the screen.
+ setScreenState(Display.STATE_OFF);
+ mPendingScreenOff = false;
+ mPowerState.dismissColorFadeResources();
+ } else if (performScreenOffTransition
+ && mPowerState.prepareColorFade(mContext,
+ mColorFadeFadesConfig
+ ? ColorFade.MODE_FADE : ColorFade.MODE_COOL_DOWN)
+ && mPowerState.getScreenState() != Display.STATE_OFF) {
+ // Perform the screen off animation.
+ mColorFadeOffAnimator.start();
+ } else {
+ // Skip the screen off animation and add a black surface to hide the
+ // contents of the screen.
+ mColorFadeOffAnimator.end();
+ }
+ }
+ }
+
+ private final Runnable mCleanListener = this::sendUpdatePowerState;
+
+ private void setProximitySensorEnabled(boolean enable) {
+ if (enable) {
+ if (!mProximitySensorEnabled) {
+ // Register the listener.
+ // Proximity sensor state already cleared initially.
+ mProximitySensorEnabled = true;
+ mIgnoreProximityUntilChanged = false;
+ mSensorManager.registerListener(mProximitySensorListener, mProximitySensor,
+ SensorManager.SENSOR_DELAY_NORMAL, mHandler);
+ }
+ } else {
+ if (mProximitySensorEnabled) {
+ // Unregister the listener.
+ // Clear the proximity sensor state for next time.
+ mProximitySensorEnabled = false;
+ mProximity = PROXIMITY_UNKNOWN;
+ mIgnoreProximityUntilChanged = false;
+ mPendingProximity = PROXIMITY_UNKNOWN;
+ mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+ mSensorManager.unregisterListener(mProximitySensorListener);
+ clearPendingProximityDebounceTime(); // release wake lock (must be last)
+ }
+ }
+ }
+
+ private void handleProximitySensorEvent(long time, boolean positive) {
+ if (mProximitySensorEnabled) {
+ if (mPendingProximity == PROXIMITY_NEGATIVE && !positive) {
+ return; // no change
+ }
+ if (mPendingProximity == PROXIMITY_POSITIVE && positive) {
+ return; // no change
+ }
+
+ // Only accept a proximity sensor reading if it remains
+ // stable for the entire debounce delay. We hold a wake lock while
+ // debouncing the sensor.
+ mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+ if (positive) {
+ mPendingProximity = PROXIMITY_POSITIVE;
+ setPendingProximityDebounceTime(
+ time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY); // acquire wake lock
+ } else {
+ mPendingProximity = PROXIMITY_NEGATIVE;
+ setPendingProximityDebounceTime(
+ time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY); // acquire wake lock
+ }
+
+ // Debounce the new sensor reading.
+ debounceProximitySensor();
+ }
+ }
+
+ private void debounceProximitySensor() {
+ if (mProximitySensorEnabled
+ && mPendingProximity != PROXIMITY_UNKNOWN
+ && mPendingProximityDebounceTime >= 0) {
+ final long now = mClock.uptimeMillis();
+ if (mPendingProximityDebounceTime <= now) {
+ if (mProximity != mPendingProximity) {
+ // if the status of the sensor changed, stop ignoring.
+ mIgnoreProximityUntilChanged = false;
+ Slog.i(mTag, "No longer ignoring proximity [" + mPendingProximity + "]");
+ }
+ // Sensor reading accepted. Apply the change then release the wake lock.
+ mProximity = mPendingProximity;
+ updatePowerState();
+ clearPendingProximityDebounceTime(); // release wake lock (must be last)
+ } else {
+ // Need to wait a little longer.
+ // Debounce again later. We continue holding a wake lock while waiting.
+ Message msg = mHandler.obtainMessage(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+ mHandler.sendMessageAtTime(msg, mPendingProximityDebounceTime);
+ }
+ }
+ }
+
+ private void clearPendingProximityDebounceTime() {
+ if (mPendingProximityDebounceTime >= 0) {
+ mPendingProximityDebounceTime = -1;
+ mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxDebounce);
+ }
+ }
+
+ private void setPendingProximityDebounceTime(long debounceTime) {
+ if (mPendingProximityDebounceTime < 0) {
+ mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxDebounce);
+ }
+ mPendingProximityDebounceTime = debounceTime;
+ }
+
+ private void sendOnStateChangedWithWakelock() {
+ if (!mOnStateChangedPending) {
+ mOnStateChangedPending = true;
+ mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdOnStateChanged);
+ mHandler.post(mOnStateChangedRunnable);
+ }
+ }
+
+ private void logDisplayPolicyChanged(int newPolicy) {
+ LogMaker log = new LogMaker(MetricsEvent.DISPLAY_POLICY);
+ log.setType(MetricsEvent.TYPE_UPDATE);
+ log.setSubtype(newPolicy);
+ MetricsLogger.action(log);
+ }
+
+ private void handleSettingsChange(boolean userSwitch) {
+ mPendingScreenBrightnessSetting = getScreenBrightnessSetting();
+ mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+ if (userSwitch) {
+ // Don't treat user switches as user initiated change.
+ setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
+ updateAutoBrightnessAdjustment();
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.resetShortTermModel();
+ }
+ }
+ // We don't bother with a pending variable for VR screen brightness since we just
+ // immediately adapt to it.
+ mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
+ sendUpdatePowerState();
+ }
+
+ private float getAutoBrightnessAdjustmentSetting() {
+ final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
+ return Float.isNaN(adj) ? 0.0f : clampAutoBrightnessAdjustment(adj);
+ }
+
+ @Override
+ public float getScreenBrightnessSetting() {
+ float brightness = mBrightnessSetting.getBrightness();
+ if (Float.isNaN(brightness)) {
+ brightness = mScreenBrightnessDefault;
+ }
+ return clampAbsoluteBrightness(brightness);
+ }
+
+ private float getScreenBrightnessForVrSetting() {
+ final float brightnessFloat = Settings.System.getFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT, mScreenBrightnessForVrDefault,
+ UserHandle.USER_CURRENT);
+ return clampScreenBrightnessForVr(brightnessFloat);
+ }
+
+ @Override
+ public void setBrightness(float brightnessValue) {
+ // Update the setting, which will eventually call back into DPC to have us actually update
+ // the display with the new value.
+ mBrightnessSetting.setBrightness(brightnessValue);
+ }
+
+ private void updateScreenBrightnessSetting(float brightnessValue) {
+ if (!isValidBrightnessValue(brightnessValue)
+ || brightnessValue == mCurrentScreenBrightnessSetting) {
+ return;
+ }
+ setCurrentScreenBrightness(brightnessValue);
+ mBrightnessSetting.setBrightness(brightnessValue);
+ }
+
+ private void setCurrentScreenBrightness(float brightnessValue) {
+ if (brightnessValue != mCurrentScreenBrightnessSetting) {
+ mCurrentScreenBrightnessSetting = brightnessValue;
+ postBrightnessChangeRunnable();
+ }
+ }
+
+ private void putAutoBrightnessAdjustmentSetting(float adjustment) {
+ if (mDisplayId == Display.DEFAULT_DISPLAY) {
+ mAutoBrightnessAdjustment = adjustment;
+ Settings.System.putFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment,
+ UserHandle.USER_CURRENT);
+ }
+ }
+
+ private boolean updateAutoBrightnessAdjustment() {
+ if (Float.isNaN(mPendingAutoBrightnessAdjustment)) {
+ return false;
+ }
+ if (mAutoBrightnessAdjustment == mPendingAutoBrightnessAdjustment) {
+ mPendingAutoBrightnessAdjustment = Float.NaN;
+ return false;
+ }
+ mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment;
+ mPendingAutoBrightnessAdjustment = Float.NaN;
+ mTemporaryAutoBrightnessAdjustment = Float.NaN;
+ return true;
+ }
+
+ // We want to return true if the user has set the screen brightness.
+ // RBC on, off, and intensity changes will return false.
+ // Slider interactions whilst in RBC will return true, just as when in non-rbc.
+ private boolean updateUserSetScreenBrightness() {
+ if ((Float.isNaN(mPendingScreenBrightnessSetting)
+ || mPendingScreenBrightnessSetting < 0.0f)) {
+ return false;
+ }
+ if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) {
+ mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ return false;
+ }
+ setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
+ mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting;
+ mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ return true;
+ }
+
+ private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
+ boolean hadUserDataPoint) {
+ final float brightnessInNits = convertToNits(brightness);
+ if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
+ && mAutomaticBrightnessController != null) {
+ // We only want to track changes on devices that can actually map the display backlight
+ // values into a physical brightness unit since the value provided by the API is in
+ // nits and not using the arbitrary backlight units.
+ final float powerFactor = mPowerRequest.lowPowerMode
+ ? mPowerRequest.screenLowPowerBrightnessFactor
+ : 1.0f;
+ mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
+ powerFactor, hadUserDataPoint,
+ mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId);
+ }
+ }
+
+ private float convertToNits(float brightness) {
+ if (mAutomaticBrightnessController == null) {
+ return -1f;
+ }
+ return mAutomaticBrightnessController.convertToNits(brightness);
+ }
+
+ @GuardedBy("mLock")
+ private void updatePendingProximityRequestsLocked() {
+ mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
+ mPendingWaitForNegativeProximityLocked = false;
+
+ if (mIgnoreProximityUntilChanged) {
+ // Also, lets stop waiting for negative proximity if we're ignoring it.
+ mWaitingForNegativeProximity = false;
+ }
+ }
+
+ private void ignoreProximitySensorUntilChangedInternal() {
+ if (!mIgnoreProximityUntilChanged
+ && mProximity == PROXIMITY_POSITIVE) {
+ // Only ignore if it is still reporting positive (near)
+ mIgnoreProximityUntilChanged = true;
+ Slog.i(mTag, "Ignoring proximity");
+ updatePowerState();
+ }
+ }
+
+ private final Runnable mOnStateChangedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mOnStateChangedPending = false;
+ mCallbacks.onStateChanged();
+ mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
+ }
+ };
+
+ private void sendOnProximityPositiveWithWakelock() {
+ mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxPositive);
+ mHandler.post(mOnProximityPositiveRunnable);
+ mOnProximityPositiveMessages++;
+ }
+
+ private final Runnable mOnProximityPositiveRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mOnProximityPositiveMessages--;
+ mCallbacks.onProximityPositive();
+ mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
+ }
+ };
+
+ private void sendOnProximityNegativeWithWakelock() {
+ mOnProximityNegativeMessages++;
+ mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxNegative);
+ mHandler.post(mOnProximityNegativeRunnable);
+ }
+
+ private final Runnable mOnProximityNegativeRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mOnProximityNegativeMessages--;
+ mCallbacks.onProximityNegative();
+ mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
+ }
+ };
+
+ @Override
+ public void dump(final PrintWriter pw) {
+ synchronized (mLock) {
+ pw.println();
+ pw.println("Display Power Controller:");
+ pw.println(" mDisplayId=" + mDisplayId);
+ pw.println(" mLightSensor=" + mLightSensor);
+
+ pw.println();
+ pw.println("Display Power Controller Locked State:");
+ pw.println(" mDisplayReadyLocked=" + mDisplayReadyLocked);
+ pw.println(" mPendingRequestLocked=" + mPendingRequestLocked);
+ pw.println(" mPendingRequestChangedLocked=" + mPendingRequestChangedLocked);
+ pw.println(" mPendingWaitForNegativeProximityLocked="
+ + mPendingWaitForNegativeProximityLocked);
+ pw.println(" mPendingUpdatePowerStateLocked=" + mPendingUpdatePowerStateLocked);
+ }
+
+ pw.println();
+ pw.println("Display Power Controller Configuration:");
+ pw.println(" mScreenBrightnessRangeDefault=" + mScreenBrightnessDefault);
+ pw.println(" mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig);
+ pw.println(" mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig);
+ pw.println(" mScreenBrightnessForVrRangeMinimum=" + mScreenBrightnessForVrRangeMinimum);
+ pw.println(" mScreenBrightnessForVrRangeMaximum=" + mScreenBrightnessForVrRangeMaximum);
+ pw.println(" mScreenBrightnessForVrDefault=" + mScreenBrightnessForVrDefault);
+ pw.println(" mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
+ pw.println(" mAllowAutoBrightnessWhileDozingConfig="
+ + mAllowAutoBrightnessWhileDozingConfig);
+ pw.println(" mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
+ pw.println(" mColorFadeFadesConfig=" + mColorFadeFadesConfig);
+ pw.println(" mColorFadeEnabled=" + mColorFadeEnabled);
+ synchronized (mCachedBrightnessInfo) {
+ pw.println(" mCachedBrightnessInfo.brightness="
+ + mCachedBrightnessInfo.brightness.value);
+ pw.println(" mCachedBrightnessInfo.adjustedBrightness="
+ + mCachedBrightnessInfo.adjustedBrightness.value);
+ pw.println(" mCachedBrightnessInfo.brightnessMin="
+ + mCachedBrightnessInfo.brightnessMin.value);
+ pw.println(" mCachedBrightnessInfo.brightnessMax="
+ + mCachedBrightnessInfo.brightnessMax.value);
+ pw.println(" mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode.value);
+ pw.println(" mCachedBrightnessInfo.hbmTransitionPoint="
+ + mCachedBrightnessInfo.hbmTransitionPoint.value);
+ pw.println(" mCachedBrightnessInfo.brightnessMaxReason ="
+ + mCachedBrightnessInfo.brightnessMaxReason.value);
+ }
+ pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
+ pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
+
+ mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
+ }
+
+ private void dumpLocal(PrintWriter pw) {
+ pw.println();
+ pw.println("Display Power Controller Thread State:");
+ pw.println(" mPowerRequest=" + mPowerRequest);
+ pw.println(" mUnfinishedBusiness=" + mUnfinishedBusiness);
+ pw.println(" mWaitingForNegativeProximity=" + mWaitingForNegativeProximity);
+ pw.println(" mProximitySensor=" + mProximitySensor);
+ pw.println(" mProximitySensorEnabled=" + mProximitySensorEnabled);
+ pw.println(" mProximityThreshold=" + mProximityThreshold);
+ pw.println(" mProximity=" + proximityToString(mProximity));
+ pw.println(" mPendingProximity=" + proximityToString(mPendingProximity));
+ pw.println(" mPendingProximityDebounceTime="
+ + TimeUtils.formatUptime(mPendingProximityDebounceTime));
+ pw.println(" mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity);
+ pw.println(" mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness);
+ pw.println(" mPendingScreenBrightnessSetting="
+ + mPendingScreenBrightnessSetting);
+ pw.println(" mTemporaryScreenBrightness=" + mTemporaryScreenBrightness);
+ pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
+ pw.println(" mBrightnessReason=" + mBrightnessReason);
+ pw.println(" mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
+ pw.println(" mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment);
+ pw.println(" mScreenBrightnessForVrFloat=" + mScreenBrightnessForVr);
+ pw.println(" mAppliedAutoBrightness=" + mAppliedAutoBrightness);
+ pw.println(" mAppliedDimming=" + mAppliedDimming);
+ pw.println(" mAppliedLowPower=" + mAppliedLowPower);
+ pw.println(" mAppliedThrottling=" + mAppliedThrottling);
+ pw.println(" mAppliedScreenBrightnessOverride=" + mAppliedScreenBrightnessOverride);
+ pw.println(" mAppliedTemporaryBrightness=" + mAppliedTemporaryBrightness);
+ pw.println(" mAppliedTemporaryAutoBrightnessAdjustment="
+ + mAppliedTemporaryAutoBrightnessAdjustment);
+ pw.println(" mAppliedBrightnessBoost=" + mAppliedBrightnessBoost);
+ pw.println(" mDozing=" + mDozing);
+ pw.println(" mSkipRampState=" + skipRampStateToString(mSkipRampState));
+ pw.println(" mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime);
+ pw.println(" mScreenOffBlockStartRealTime=" + mScreenOffBlockStartRealTime);
+ pw.println(" mPendingScreenOnUnblocker=" + mPendingScreenOnUnblocker);
+ pw.println(" mPendingScreenOffUnblocker=" + mPendingScreenOffUnblocker);
+ pw.println(" mPendingScreenOff=" + mPendingScreenOff);
+ pw.println(" mReportedToPolicy="
+ + reportedToPolicyToString(mReportedScreenStateToPolicy));
+ pw.println(" mIsRbcActive=" + mIsRbcActive);
+ pw.println(" mOnStateChangePending=" + mOnStateChangedPending);
+ pw.println(" mOnProximityPositiveMessages=" + mOnProximityPositiveMessages);
+ pw.println(" mOnProximityNegativeMessages=" + mOnProximityNegativeMessages);
+
+ if (mScreenBrightnessRampAnimator != null) {
+ pw.println(" mScreenBrightnessRampAnimator.isAnimating()="
+ + mScreenBrightnessRampAnimator.isAnimating());
+ }
+
+ if (mColorFadeOnAnimator != null) {
+ pw.println(" mColorFadeOnAnimator.isStarted()="
+ + mColorFadeOnAnimator.isStarted());
+ }
+ if (mColorFadeOffAnimator != null) {
+ pw.println(" mColorFadeOffAnimator.isStarted()="
+ + mColorFadeOffAnimator.isStarted());
+ }
+
+ if (mPowerState != null) {
+ mPowerState.dump(pw);
+ }
+
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.dump(pw);
+ dumpBrightnessEvents(pw);
+ }
+
+ if (mHbmController != null) {
+ mHbmController.dump(pw);
+ }
+
+ if (mBrightnessThrottler != null) {
+ mBrightnessThrottler.dump(pw);
+ }
+
+ pw.println();
+ if (mDisplayWhiteBalanceController != null) {
+ mDisplayWhiteBalanceController.dump(pw);
+ mDisplayWhiteBalanceSettings.dump(pw);
+ }
+ }
+
+ private static String proximityToString(int state) {
+ switch (state) {
+ case PROXIMITY_UNKNOWN:
+ return "Unknown";
+ case PROXIMITY_NEGATIVE:
+ return "Negative";
+ case PROXIMITY_POSITIVE:
+ return "Positive";
+ default:
+ return Integer.toString(state);
+ }
+ }
+
+ private static String reportedToPolicyToString(int state) {
+ switch (state) {
+ case REPORTED_TO_POLICY_SCREEN_OFF:
+ return "REPORTED_TO_POLICY_SCREEN_OFF";
+ case REPORTED_TO_POLICY_SCREEN_TURNING_ON:
+ return "REPORTED_TO_POLICY_SCREEN_TURNING_ON";
+ case REPORTED_TO_POLICY_SCREEN_ON:
+ return "REPORTED_TO_POLICY_SCREEN_ON";
+ default:
+ return Integer.toString(state);
+ }
+ }
+
+ private static String skipRampStateToString(int state) {
+ switch (state) {
+ case RAMP_STATE_SKIP_NONE:
+ return "RAMP_STATE_SKIP_NONE";
+ case RAMP_STATE_SKIP_INITIAL:
+ return "RAMP_STATE_SKIP_INITIAL";
+ case RAMP_STATE_SKIP_AUTOBRIGHT:
+ return "RAMP_STATE_SKIP_AUTOBRIGHT";
+ default:
+ return Integer.toString(state);
+ }
+ }
+
+ private void dumpBrightnessEvents(PrintWriter pw) {
+ int size = mBrightnessEventRingBuffer.size();
+ if (size < 1) {
+ pw.println("No Automatic Brightness Adjustments");
+ return;
+ }
+
+ pw.println("Automatic Brightness Adjustments Last " + size + " Events: ");
+ BrightnessEvent[] eventArray = mBrightnessEventRingBuffer.toArray();
+ for (int i = 0; i < mBrightnessEventRingBuffer.size(); i++) {
+ pw.println(" " + eventArray[i].toString());
+ }
+ }
+
+ private static float clampAbsoluteBrightness(float value) {
+ return MathUtils.constrain(value, PowerManager.BRIGHTNESS_MIN,
+ PowerManager.BRIGHTNESS_MAX);
+ }
+
+ private static float clampAutoBrightnessAdjustment(float value) {
+ return MathUtils.constrain(value, -1.0f, 1.0f);
+ }
+
+ private void noteScreenState(int screenState) {
+ if (mBatteryStats != null) {
+ try {
+ // TODO(multi-display): make this multi-display
+ mBatteryStats.noteScreenState(screenState);
+ } catch (RemoteException e) {
+ // same process
+ }
+ }
+ }
+
+ private void noteScreenBrightness(float brightness) {
+ if (mBatteryStats != null) {
+ try {
+ // TODO(brightnessfloat): change BatteryStats to use float
+ mBatteryStats.noteScreenBrightness(BrightnessSynchronizer.brightnessFloatToInt(
+ brightness));
+ } catch (RemoteException e) {
+ // same process
+ }
+ }
+ }
+
+ private void reportStats(float brightness) {
+ if (mLastStatsBrightness == brightness) {
+ return;
+ }
+
+ float hbmTransitionPoint = PowerManager.BRIGHTNESS_MAX;
+ synchronized (mCachedBrightnessInfo) {
+ if (mCachedBrightnessInfo.hbmTransitionPoint == null) {
+ return;
+ }
+ hbmTransitionPoint = mCachedBrightnessInfo.hbmTransitionPoint.value;
+ }
+
+ final boolean aboveTransition = brightness > hbmTransitionPoint;
+ final boolean oldAboveTransition = mLastStatsBrightness > hbmTransitionPoint;
+
+ if (aboveTransition || oldAboveTransition) {
+ mLastStatsBrightness = brightness;
+ mHandler.removeMessages(MSG_STATSD_HBM_BRIGHTNESS);
+ if (aboveTransition != oldAboveTransition) {
+ // report immediately
+ logHbmBrightnessStats(brightness, mDisplayStatsId);
+ } else {
+ // delay for rate limiting
+ Message msg = mHandler.obtainMessage();
+ msg.what = MSG_STATSD_HBM_BRIGHTNESS;
+ msg.arg1 = Float.floatToIntBits(brightness);
+ msg.arg2 = mDisplayStatsId;
+ mHandler.sendMessageDelayed(msg, BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS);
+ }
+ }
+ }
+
+ private void logHbmBrightnessStats(float brightness, int displayStatsId) {
+ synchronized (mHandler) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.DISPLAY_HBM_BRIGHTNESS_CHANGED, displayStatsId, brightness);
+ }
+ }
+
+ private void logManualBrightnessEvent(BrightnessEvent event) {
+ float appliedLowPowerMode = event.isLowPowerModeSet() ? event.getPowerFactor() : -1f;
+ int appliedRbcStrength = event.isRbcEnabled() ? event.getRbcStrength() : -1;
+ float appliedHbmMaxNits =
+ event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
+ ? -1f : convertToNits(event.getHbmMax());
+ // thermalCapNits set to -1 if not currently capping max brightness
+ float appliedThermalCapNits =
+ event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
+ ? -1f : convertToNits(event.getThermalMax());
+
+ FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
+ convertToNits(event.getInitialBrightness()),
+ convertToNits(event.getBrightness()),
+ event.getSlowAmbientLux(),
+ event.getPhysicalDisplayId(),
+ event.isShortTermModelActive(),
+ appliedLowPowerMode,
+ appliedRbcStrength,
+ appliedHbmMaxNits,
+ appliedThermalCapNits,
+ event.isAutomaticBrightnessEnabled(),
+ FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+ }
+
+ private final class DisplayControllerHandler extends Handler {
+ DisplayControllerHandler(Looper looper) {
+ super(looper, null, true /*async*/);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE_POWER_STATE:
+ updatePowerState();
+ break;
+
+ case MSG_PROXIMITY_SENSOR_DEBOUNCED:
+ debounceProximitySensor();
+ break;
+
+ case MSG_SCREEN_ON_UNBLOCKED:
+ if (mPendingScreenOnUnblocker == msg.obj) {
+ unblockScreenOn();
+ updatePowerState();
+ }
+ break;
+ case MSG_SCREEN_OFF_UNBLOCKED:
+ if (mPendingScreenOffUnblocker == msg.obj) {
+ unblockScreenOff();
+ updatePowerState();
+ }
+ break;
+ case MSG_CONFIGURE_BRIGHTNESS:
+ mBrightnessConfiguration = (BrightnessConfiguration) msg.obj;
+ updatePowerState();
+ break;
+
+ case MSG_SET_TEMPORARY_BRIGHTNESS:
+ // TODO: Should we have a a timeout for the temporary brightness?
+ mTemporaryScreenBrightness = Float.intBitsToFloat(msg.arg1);
+ updatePowerState();
+ break;
+
+ case MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT:
+ mTemporaryAutoBrightnessAdjustment = Float.intBitsToFloat(msg.arg1);
+ updatePowerState();
+ break;
+
+ case MSG_IGNORE_PROXIMITY:
+ ignoreProximitySensorUntilChangedInternal();
+ break;
+
+ case MSG_STOP:
+ cleanupHandlerThreadAfterStop();
+ break;
+
+ case MSG_UPDATE_BRIGHTNESS:
+ if (mStopped) {
+ return;
+ }
+ handleSettingsChange(false /*userSwitch*/);
+ break;
+
+ case MSG_UPDATE_RBC:
+ handleRbcChanged();
+ break;
+
+ case MSG_BRIGHTNESS_RAMP_DONE:
+ if (mPowerState != null) {
+ final float brightness = mPowerState.getScreenBrightness();
+ reportStats(brightness);
+ }
+ break;
+
+ case MSG_STATSD_HBM_BRIGHTNESS:
+ logHbmBrightnessStats(Float.intBitsToFloat(msg.arg1), msg.arg2);
+ break;
+ }
+ }
+ }
+
+ private final SensorEventListener mProximitySensorListener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (mProximitySensorEnabled) {
+ final long time = mClock.uptimeMillis();
+ final float distance = event.values[0];
+ boolean positive = distance >= 0.0f && distance < mProximityThreshold;
+ handleProximitySensorEvent(time, positive);
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ // Not used.
+ }
+ };
+
+
+ private final class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ handleSettingsChange(false /* userSwitch */);
+ }
+ }
+
+ private final class ScreenOnUnblocker implements WindowManagerPolicy.ScreenOnListener {
+ @Override
+ public void onScreenOn() {
+ Message msg = mHandler.obtainMessage(MSG_SCREEN_ON_UNBLOCKED, this);
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ private final class ScreenOffUnblocker implements WindowManagerPolicy.ScreenOffListener {
+ @Override
+ public void onScreenOff() {
+ Message msg = mHandler.obtainMessage(MSG_SCREEN_OFF_UNBLOCKED, this);
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ @Override
+ public void setAutoBrightnessLoggingEnabled(boolean enabled) {
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.setLoggingEnabled(enabled);
+ }
+ }
+
+ @Override // DisplayWhiteBalanceController.Callbacks
+ public void updateWhiteBalance() {
+ sendUpdatePowerState();
+ }
+
+ @Override
+ public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
+ if (mDisplayWhiteBalanceController != null) {
+ mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
+ mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
+ }
+ }
+
+ @Override
+ public void setAmbientColorTemperatureOverride(float cct) {
+ if (mDisplayWhiteBalanceController != null) {
+ mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
+ // The ambient color temperature override is only applied when the ambient color
+ // temperature changes or is updated, so it doesn't necessarily change the screen color
+ // temperature immediately. So, let's make it!
+ sendUpdatePowerState();
+ }
+ }
+
+ @VisibleForTesting
+ String getSuspendBlockerUnfinishedBusinessId(int displayId) {
+ return "[" + displayId + "]unfinished business";
+ }
+
+ String getSuspendBlockerOnStateChangedId(int displayId) {
+ return "[" + displayId + "]on state changed";
+ }
+
+ String getSuspendBlockerProxPositiveId(int displayId) {
+ return "[" + displayId + "]prox positive";
+ }
+
+ String getSuspendBlockerProxNegativeId(int displayId) {
+ return "[" + displayId + "]prox negative";
+ }
+
+ @VisibleForTesting
+ String getSuspendBlockerProxDebounceId(int displayId) {
+ return "[" + displayId + "]prox debounce";
+ }
+
+ /** Functional interface for providing time. */
+ @VisibleForTesting
+ interface Clock {
+ /**
+ * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
+ */
+ long uptimeMillis();
+ }
+
+ @VisibleForTesting
+ static class Injector {
+ Clock getClock() {
+ return SystemClock::uptimeMillis;
+ }
+
+ DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
+ int displayId, int displayState) {
+ return new DisplayPowerState(blanker, colorFade, displayId, displayState);
+ }
+
+ DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
+ FloatProperty<DisplayPowerState> firstProperty,
+ FloatProperty<DisplayPowerState> secondProperty) {
+ return new DualRampAnimator(dps, firstProperty, secondProperty);
+ }
+ }
+
+ static class CachedBrightnessInfo {
+ public MutableFloat brightness = new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ public MutableFloat adjustedBrightness =
+ new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ public MutableFloat brightnessMin =
+ new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ public MutableFloat brightnessMax =
+ new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ public MutableInt hbmMode = new MutableInt(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
+ public MutableFloat hbmTransitionPoint =
+ new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID);
+ public MutableInt brightnessMaxReason =
+ new MutableInt(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
+
+ public boolean checkAndSetFloat(MutableFloat mf, float f) {
+ if (mf.value != f) {
+ mf.value = f;
+ return true;
+ }
+ return false;
+ }
+
+ public boolean checkAndSetInt(MutableInt mi, int i) {
+ if (mi.value != i) {
+ mi.value = i;
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index 3413489..abf8fe3 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -36,8 +36,7 @@
private final float mMinBrightening;
/**
- * Creates a {@code HysteresisLevels} object with the given equal-length
- * integer arrays.
+ * Creates a {@code HysteresisLevels} object for ambient brightness.
* @param brighteningThresholds an array of brightening hysteresis constraint constants.
* @param darkeningThresholds an array of darkening hysteresis constraint constants.
* @param thresholdLevels a monotonically increasing array of threshold levels.
@@ -59,6 +58,28 @@
}
/**
+ * Creates a {@code HysteresisLevels} object for screen brightness.
+ * @param brighteningThresholds an array of brightening hysteresis constraint constants.
+ * @param darkeningThresholds an array of darkening hysteresis constraint constants.
+ * @param thresholdLevels a monotonically increasing array of threshold levels.
+ * @param minBrighteningThreshold the minimum value for which the brightening value needs to
+ * return.
+ * @param minDarkeningThreshold the minimum value for which the darkening value needs to return.
+ */
+ HysteresisLevels(int[] brighteningThresholds, int[] darkeningThresholds,
+ float[] thresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold) {
+ if (brighteningThresholds.length != darkeningThresholds.length
+ || darkeningThresholds.length != thresholdLevels.length + 1) {
+ throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
+ }
+ mBrighteningThresholds = setArrayFormat(brighteningThresholds, 1000.0f);
+ mDarkeningThresholds = setArrayFormat(darkeningThresholds, 1000.0f);
+ mThresholdLevels = constraintInRangeIfNeeded(thresholdLevels);
+ mMinDarkening = minDarkeningThreshold;
+ mMinBrightening = minBrighteningThreshold;
+ }
+
+ /**
* Return the brightening hysteresis threshold for the given value level.
*/
public float getBrighteningThreshold(float value) {
@@ -104,11 +125,42 @@
private float[] setArrayFormat(int[] configArray, float divideFactor) {
float[] levelArray = new float[configArray.length];
for (int index = 0; levelArray.length > index; ++index) {
- levelArray[index] = (float)configArray[index] / divideFactor;
+ levelArray[index] = (float) configArray[index] / divideFactor;
}
return levelArray;
}
+ /**
+ * This check is due to historical reasons, where screen thresholdLevels used to be
+ * integer values in the range of [0-255], but then was changed to be float values from [0,1].
+ * To accommodate both the possibilities, we first check if all the thresholdLevels are in [0,
+ * 1], and if not, we divide all the levels with 255 to bring them down to the same scale.
+ */
+ private float[] constraintInRangeIfNeeded(float[] thresholdLevels) {
+ if (isAllInRange(thresholdLevels, /* minValueInclusive = */ 0.0f, /* maxValueInclusive = */
+ 1.0f)) {
+ return thresholdLevels;
+ }
+
+ Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale");
+ float[] thresholdLevelsScaled = new float[thresholdLevels.length];
+ for (int index = 0; thresholdLevels.length > index; ++index) {
+ thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f;
+ }
+ return thresholdLevelsScaled;
+ }
+
+ private boolean isAllInRange(float[] configArray, float minValueInclusive,
+ float maxValueInclusive) {
+ int configArraySize = configArray.length;
+ for (int index = 0; configArraySize > index; ++index) {
+ if (configArray[index] < minValueInclusive || configArray[index] > maxValueInclusive) {
+ return false;
+ }
+ }
+ return true;
+ }
+
void dump(PrintWriter pw) {
pw.println("HysteresisLevels");
pw.println(" mBrighteningThresholds=" + Arrays.toString(mBrighteningThresholds));
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index b51f3f5..21a8518 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -50,6 +50,7 @@
import android.hardware.display.ColorDisplayManager;
import android.hardware.display.ColorDisplayManager.AutoMode;
import android.hardware.display.ColorDisplayManager.ColorMode;
+import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.IColorDisplayManager;
import android.hardware.display.Time;
import android.net.Uri;
@@ -154,7 +155,8 @@
@VisibleForTesting
final DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController =
- new DisplayWhiteBalanceTintController();
+ new DisplayWhiteBalanceTintController(
+ LocalServices.getService(DisplayManagerInternal.class));
private final NightDisplayTintController mNightDisplayTintController =
new NightDisplayTintController();
private final TintController mGlobalSaturationTintController =
diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
index 93a78c1..c5dd6ac 100644
--- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
+++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
@@ -16,6 +16,8 @@
package com.android.server.display.color;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
import android.annotation.NonNull;
@@ -24,10 +26,9 @@
import android.content.res.Resources;
import android.graphics.ColorSpace;
import android.hardware.display.ColorDisplayManager;
+import android.hardware.display.DisplayManagerInternal;
import android.opengl.Matrix;
-import android.os.IBinder;
import android.util.Slog;
-import android.view.SurfaceControl;
import android.view.SurfaceControl.DisplayPrimaries;
import com.android.internal.R;
@@ -64,6 +65,12 @@
// This feature becomes disallowed if the device is in an unsupported strong/light state.
private boolean mIsAllowed = true;
+ private final DisplayManagerInternal mDisplayManagerInternal;
+
+ DisplayWhiteBalanceTintController(DisplayManagerInternal dm) {
+ mDisplayManagerInternal = dm;
+ }
+
@Override
public void setUp(Context context, boolean needsLinear) {
mSetUp = false;
@@ -287,12 +294,8 @@
}
private ColorSpace.Rgb getDisplayColorSpaceFromSurfaceControl() {
- final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
- if (displayToken == null) {
- return null;
- }
-
- DisplayPrimaries primaries = SurfaceControl.getDisplayNativePrimaries(displayToken);
+ DisplayPrimaries primaries =
+ mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY);
if (primaries == null || primaries.red == null || primaries.green == null
|| primaries.blue == null || primaries.white == null) {
return null;
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 1d1057f..9bd48f2 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -115,6 +115,7 @@
import com.android.server.location.injector.LocationPermissionsHelper;
import com.android.server.location.injector.LocationPowerSaveModeHelper;
import com.android.server.location.injector.LocationUsageLogger;
+import com.android.server.location.injector.PackageResetHelper;
import com.android.server.location.injector.ScreenInteractiveHelper;
import com.android.server.location.injector.SettingsHelper;
import com.android.server.location.injector.SystemAlarmHelper;
@@ -125,6 +126,7 @@
import com.android.server.location.injector.SystemEmergencyHelper;
import com.android.server.location.injector.SystemLocationPermissionsHelper;
import com.android.server.location.injector.SystemLocationPowerSaveModeHelper;
+import com.android.server.location.injector.SystemPackageResetHelper;
import com.android.server.location.injector.SystemScreenInteractiveHelper;
import com.android.server.location.injector.SystemSettingsHelper;
import com.android.server.location.injector.SystemUserInfoHelper;
@@ -1696,11 +1698,13 @@
private final SystemDeviceStationaryHelper mDeviceStationaryHelper;
private final SystemDeviceIdleHelper mDeviceIdleHelper;
private final LocationUsageLogger mLocationUsageLogger;
+ private final PackageResetHelper mPackageResetHelper;
// lazily instantiated since they may not always be used
@GuardedBy("this")
- private @Nullable SystemEmergencyHelper mEmergencyCallHelper;
+ @Nullable
+ private SystemEmergencyHelper mEmergencyCallHelper;
@GuardedBy("this")
private boolean mSystemReady;
@@ -1721,6 +1725,7 @@
mDeviceStationaryHelper = new SystemDeviceStationaryHelper();
mDeviceIdleHelper = new SystemDeviceIdleHelper(context);
mLocationUsageLogger = new LocationUsageLogger();
+ mPackageResetHelper = new SystemPackageResetHelper(context);
}
synchronized void onSystemReady() {
@@ -1811,5 +1816,10 @@
public LocationUsageLogger getLocationUsageLogger() {
return mLocationUsageLogger;
}
+
+ @Override
+ public PackageResetHelper getPackageResetHelper() {
+ return mPackageResetHelper;
+ }
}
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index 82bcca2..e7f6e67 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -39,6 +39,7 @@
import com.android.server.location.injector.AppForegroundHelper;
import com.android.server.location.injector.Injector;
import com.android.server.location.injector.LocationPermissionsHelper;
+import com.android.server.location.injector.PackageResetHelper;
import com.android.server.location.injector.SettingsHelper;
import com.android.server.location.injector.UserInfoHelper;
import com.android.server.location.injector.UserInfoHelper.UserListener;
@@ -193,6 +194,7 @@
protected final LocationPermissionsHelper mLocationPermissionsHelper;
protected final AppForegroundHelper mAppForegroundHelper;
protected final LocationManagerInternal mLocationManagerInternal;
+ private final PackageResetHelper mPackageResetHelper;
private final UserListener mUserChangedListener = this::onUserChanged;
private final ProviderEnabledListener mProviderEnabledChangedListener =
@@ -218,12 +220,25 @@
};
private final AppForegroundHelper.AppForegroundListener mAppForegroundChangedListener =
this::onAppForegroundChanged;
+ private final PackageResetHelper.Responder mPackageResetResponder =
+ new PackageResetHelper.Responder() {
+ @Override
+ public void onPackageReset(String packageName) {
+ GnssListenerMultiplexer.this.onPackageReset(packageName);
+ }
+
+ @Override
+ public boolean isResetableForPackage(String packageName) {
+ return GnssListenerMultiplexer.this.isResetableForPackage(packageName);
+ }
+ };
protected GnssListenerMultiplexer(Injector injector) {
mUserInfoHelper = injector.getUserInfoHelper();
mSettingsHelper = injector.getSettingsHelper();
mLocationPermissionsHelper = injector.getLocationPermissionsHelper();
mAppForegroundHelper = injector.getAppForegroundHelper();
+ mPackageResetHelper = injector.getPackageResetHelper();
mLocationManagerInternal = Objects.requireNonNull(
LocalServices.getService(LocationManagerInternal.class));
}
@@ -357,6 +372,7 @@
mLocationPackageBlacklistChangedListener);
mLocationPermissionsHelper.addListener(mLocationPermissionsListener);
mAppForegroundHelper.addListener(mAppForegroundChangedListener);
+ mPackageResetHelper.register(mPackageResetResponder);
}
@Override
@@ -374,6 +390,7 @@
mLocationPackageBlacklistChangedListener);
mLocationPermissionsHelper.removeListener(mLocationPermissionsListener);
mAppForegroundHelper.removeListener(mAppForegroundChangedListener);
+ mPackageResetHelper.unregister(mPackageResetResponder);
}
private void onUserChanged(int userId, int change) {
@@ -407,6 +424,27 @@
updateRegistrations(registration -> registration.onForegroundChanged(uid, foreground));
}
+ private void onPackageReset(String packageName) {
+ // invoked when a package is "force quit" - move off the main thread
+ FgThread.getExecutor().execute(
+ () ->
+ updateRegistrations(
+ registration -> {
+ if (registration.getIdentity().getPackageName().equals(
+ packageName)) {
+ registration.remove();
+ }
+
+ return false;
+ }));
+ }
+
+ private boolean isResetableForPackage(String packageName) {
+ // invoked to find out if the given package has any state that can be "force quit"
+ return findRegistration(
+ registration -> registration.getIdentity().getPackageName().equals(packageName));
+ }
+
@Override
protected String getServiceState() {
if (!isSupported()) {
diff --git a/services/core/java/com/android/server/location/injector/Injector.java b/services/core/java/com/android/server/location/injector/Injector.java
index c0ce3a6..b2c8672 100644
--- a/services/core/java/com/android/server/location/injector/Injector.java
+++ b/services/core/java/com/android/server/location/injector/Injector.java
@@ -63,4 +63,7 @@
/** Returns a LocationUsageLogger. */
LocationUsageLogger getLocationUsageLogger();
+
+ /** Returns a PackageResetHelper. */
+ PackageResetHelper getPackageResetHelper();
}
diff --git a/services/core/java/com/android/server/location/injector/PackageResetHelper.java b/services/core/java/com/android/server/location/injector/PackageResetHelper.java
new file mode 100644
index 0000000..721c576
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/PackageResetHelper.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.injector;
+
+import static com.android.server.location.LocationManagerService.D;
+import static com.android.server.location.LocationManagerService.TAG;
+
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/** Helpers for tracking queries and resets of package state. */
+public abstract class PackageResetHelper {
+
+ /** Interface for responding to reset events. */
+ public interface Responder {
+
+ /**
+ * Called when a package's runtime state is being reset for whatever reason and any
+ * components carrying runtime state on behalf of the package should clear that state.
+ *
+ * @param packageName The name of the package.
+ */
+ void onPackageReset(String packageName);
+
+ /**
+ * Called when the system queries whether this package has any active state for the given
+ * package. Should return true if the component has some runtime state that is resetable of
+ * behalf of the given package, and false otherwise.
+ *
+ * @param packageName The name of the package.
+ * @return True if this component has resetable state for the given package.
+ */
+ boolean isResetableForPackage(String packageName);
+ }
+
+ private final CopyOnWriteArrayList<Responder> mResponders;
+
+ public PackageResetHelper() {
+ mResponders = new CopyOnWriteArrayList<>();
+ }
+
+ /** Begin listening for package reset events. */
+ public synchronized void register(Responder responder) {
+ boolean empty = mResponders.isEmpty();
+ mResponders.add(responder);
+ if (empty) {
+ onRegister();
+ }
+ }
+
+ /** Stop listening for package reset events. */
+ public synchronized void unregister(Responder responder) {
+ mResponders.remove(responder);
+ if (mResponders.isEmpty()) {
+ onUnregister();
+ }
+ }
+
+ @GuardedBy("this")
+ protected abstract void onRegister();
+
+ @GuardedBy("this")
+ protected abstract void onUnregister();
+
+ protected final void notifyPackageReset(String packageName) {
+ if (D) {
+ Log.d(TAG, "package " + packageName + " reset");
+ }
+
+ for (Responder responder : mResponders) {
+ responder.onPackageReset(packageName);
+ }
+ }
+
+ protected final boolean queryResetableForPackage(String packageName) {
+ for (Responder responder : mResponders) {
+ if (responder.isResetableForPackage(packageName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java b/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java
new file mode 100644
index 0000000..91b0212
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.injector;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+
+import com.android.internal.util.Preconditions;
+
+/** Listens to appropriate broadcasts for queries and resets. */
+public class SystemPackageResetHelper extends PackageResetHelper {
+
+ private final Context mContext;
+
+ @Nullable
+ private BroadcastReceiver mReceiver;
+
+ public SystemPackageResetHelper(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ protected void onRegister() {
+ Preconditions.checkState(mReceiver == null);
+ mReceiver = new Receiver();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
+ filter.addDataScheme("package");
+
+ // We don't filter for Intent.ACTION_PACKAGE_DATA_CLEARED as 1) it refers to persistent
+ // data, and 2) it should always be preceded by Intent.ACTION_PACKAGE_RESTARTED, which
+ // refers to runtime data. in this way we also avoid redundant callbacks.
+
+ mContext.registerReceiver(mReceiver, filter);
+ }
+
+ @Override
+ protected void onUnregister() {
+ Preconditions.checkState(mReceiver != null);
+ mContext.unregisterReceiver(mReceiver);
+ mReceiver = null;
+ }
+
+ private class Receiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+
+ Uri data = intent.getData();
+ if (data == null) {
+ return;
+ }
+
+ String packageName = data.getSchemeSpecificPart();
+ if (packageName == null) {
+ return;
+ }
+
+ switch (action) {
+ case Intent.ACTION_QUERY_PACKAGE_RESTART:
+ String[] packages = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
+ if (packages != null) {
+ // it would be more efficient to pass through the whole array, but at the
+ // moment the array is always size 1, and this makes for a nicer callback.
+ for (String pkg : packages) {
+ if (queryResetableForPackage(pkg)) {
+ setResultCode(Activity.RESULT_OK);
+ break;
+ }
+ }
+ }
+ break;
+ case Intent.ACTION_PACKAGE_CHANGED:
+ // make sure this is an enabled/disabled change to the package as a whole, not
+ // just some of its components. This avoids unnecessary work in the callback.
+ boolean isPackageChange = false;
+ String[] components = intent.getStringArrayExtra(
+ Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+ if (components != null) {
+ for (String component : components) {
+ if (packageName.equals(component)) {
+ isPackageChange = true;
+ break;
+ }
+ }
+ }
+
+ if (isPackageChange) {
+ try {
+ ApplicationInfo appInfo =
+ context.getPackageManager().getApplicationInfo(packageName,
+ PackageManager.ApplicationInfoFlags.of(0));
+ if (!appInfo.enabled) {
+ notifyPackageReset(packageName);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ return;
+ }
+ }
+ break;
+ case Intent.ACTION_PACKAGE_REMOVED:
+ // fall through
+ case Intent.ACTION_PACKAGE_RESTARTED:
+ notifyPackageReset(packageName);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index a69a079..bd75251 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -106,6 +106,7 @@
import com.android.server.location.injector.LocationPowerSaveModeHelper;
import com.android.server.location.injector.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener;
import com.android.server.location.injector.LocationUsageLogger;
+import com.android.server.location.injector.PackageResetHelper;
import com.android.server.location.injector.ScreenInteractiveHelper;
import com.android.server.location.injector.ScreenInteractiveHelper.ScreenInteractiveChangedListener;
import com.android.server.location.injector.SettingsHelper;
@@ -1373,6 +1374,7 @@
protected final ScreenInteractiveHelper mScreenInteractiveHelper;
protected final LocationUsageLogger mLocationUsageLogger;
protected final LocationFudger mLocationFudger;
+ private final PackageResetHelper mPackageResetHelper;
private final UserListener mUserChangedListener = this::onUserChanged;
private final LocationSettings.LocationUserSettingsListener mLocationUserSettingsListener =
@@ -1407,6 +1409,18 @@
this::onLocationPowerSaveModeChanged;
private final ScreenInteractiveChangedListener mScreenInteractiveChangedListener =
this::onScreenInteractiveChanged;
+ private final PackageResetHelper.Responder mPackageResetResponder =
+ new PackageResetHelper.Responder() {
+ @Override
+ public void onPackageReset(String packageName) {
+ LocationProviderManager.this.onPackageReset(packageName);
+ }
+
+ @Override
+ public boolean isResetableForPackage(String packageName) {
+ return LocationProviderManager.this.isResetableForPackage(packageName);
+ }
+ };
// acquiring mMultiplexerLock makes operations on mProvider atomic, but is otherwise unnecessary
protected final MockableLocationProvider mProvider;
@@ -1442,6 +1456,7 @@
mScreenInteractiveHelper = injector.getScreenInteractiveHelper();
mLocationUsageLogger = injector.getLocationUsageLogger();
mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
+ mPackageResetHelper = injector.getPackageResetHelper();
mProvider = new MockableLocationProvider(mMultiplexerLock);
@@ -1970,6 +1985,7 @@
mAppForegroundHelper.addListener(mAppForegroundChangedListener);
mLocationPowerSaveModeHelper.addListener(mLocationPowerSaveModeChangedListener);
mScreenInteractiveHelper.addListener(mScreenInteractiveChangedListener);
+ mPackageResetHelper.register(mPackageResetResponder);
}
@GuardedBy("mMultiplexerLock")
@@ -1988,6 +2004,7 @@
mAppForegroundHelper.removeListener(mAppForegroundChangedListener);
mLocationPowerSaveModeHelper.removeListener(mLocationPowerSaveModeChangedListener);
mScreenInteractiveHelper.removeListener(mScreenInteractiveChangedListener);
+ mPackageResetHelper.unregister(mPackageResetResponder);
}
@GuardedBy("mMultiplexerLock")
@@ -2391,6 +2408,27 @@
updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
}
+ private void onPackageReset(String packageName) {
+ // invoked when a package is "force quit" - move off the main thread
+ FgThread.getExecutor().execute(
+ () ->
+ updateRegistrations(
+ registration -> {
+ if (registration.getIdentity().getPackageName().equals(
+ packageName)) {
+ registration.remove();
+ }
+
+ return false;
+ }));
+ }
+
+ private boolean isResetableForPackage(String packageName) {
+ // invoked to find out if the given package has any state that can be "force quit"
+ return findRegistration(
+ registration -> registration.getIdentity().getPackageName().equals(packageName));
+ }
+
@GuardedBy("mMultiplexerLock")
@Override
public void onStateChanged(
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index bfa8af9..92a6329 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -601,6 +601,26 @@
}
}
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ pw.println(prefix + "MediaRouter2ServiceImpl");
+
+ String indent = prefix + " ";
+
+ synchronized (mLock) {
+ pw.println(indent + "mNextRouterOrManagerId=" + mNextRouterOrManagerId.get());
+ pw.println(indent + "mCurrentUserId=" + mCurrentUserId);
+
+ pw.println(indent + "UserRecords:");
+ if (mUserRecords.size() > 0) {
+ for (int i = 0; i < mUserRecords.size(); i++) {
+ mUserRecords.get(i).dump(pw, indent + " ");
+ }
+ } else {
+ pw.println(indent + "<no user records>");
+ }
+ }
+ }
+
//TODO(b/136703681): Review this is handling multi-user properly.
void switchUser() {
synchronized (mLock) {
@@ -1197,6 +1217,41 @@
}
return null;
}
+
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ pw.println(prefix + "UserRecord");
+
+ String indent = prefix + " ";
+
+ pw.println(indent + "mUserId=" + mUserId);
+
+ pw.println(indent + "Router Records:");
+ if (!mRouterRecords.isEmpty()) {
+ for (RouterRecord routerRecord : mRouterRecords) {
+ routerRecord.dump(pw, indent + " ");
+ }
+ } else {
+ pw.println(indent + "<no router records>");
+ }
+
+ pw.println(indent + "Manager Records:");
+ if (!mManagerRecords.isEmpty()) {
+ for (ManagerRecord managerRecord : mManagerRecords) {
+ managerRecord.dump(pw, indent + " ");
+ }
+ } else {
+ pw.println(indent + "<no manager records>");
+ }
+
+ if (!mHandler.runWithScissors(new Runnable() {
+ @Override
+ public void run() {
+ mHandler.dump(pw, indent);
+ }
+ }, 1000)) {
+ pw.println(indent + "<could not dump handler state>");
+ }
+ }
}
final class RouterRecord implements IBinder.DeathRecipient {
@@ -1236,6 +1291,22 @@
public void binderDied() {
routerDied(this);
}
+
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ pw.println(prefix + "RouterRecord");
+
+ String indent = prefix + " ";
+
+ pw.println(indent + "mPackageName=" + mPackageName);
+ pw.println(indent + "mSelectRouteSequenceNumbers=" + mSelectRouteSequenceNumbers);
+ pw.println(indent + "mUid=" + mUid);
+ pw.println(indent + "mPid=" + mPid);
+ pw.println(indent + "mHasConfigureWifiDisplayPermission="
+ + mHasConfigureWifiDisplayPermission);
+ pw.println(indent + "mHasModifyAudioRoutingPermission="
+ + mHasModifyAudioRoutingPermission);
+ pw.println(indent + "mRouterId=" + mRouterId);
+ }
}
final class ManagerRecord implements IBinder.DeathRecipient {
@@ -1267,8 +1338,20 @@
managerDied(this);
}
- public void dump(PrintWriter pw, String prefix) {
- pw.println(prefix + this);
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ pw.println(prefix + "ManagerRecord");
+
+ String indent = prefix + " ";
+
+ pw.println(indent + "mPackageName=" + mPackageName);
+ pw.println(indent + "mManagerId=" + mManagerId);
+ pw.println(indent + "mUid=" + mUid);
+ pw.println(indent + "mPid=" + mPid);
+ pw.println(indent + "mIsScanning=" + mIsScanning);
+
+ if (mLastSessionCreationRequest != null) {
+ mLastSessionCreationRequest.dump(pw, indent);
+ }
}
public void startScan() {
@@ -1455,6 +1538,15 @@
}
}
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ pw.println(prefix + "UserHandler");
+
+ String indent = prefix + " ";
+ pw.println(indent + "mRunning=" + mRunning);
+
+ mWatcher.dump(pw, prefix);
+ }
+
private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
MediaRoute2ProviderInfo currentInfo = provider.getProviderInfo();
@@ -2340,5 +2432,14 @@
mOldSession = oldSession;
mRoute = route;
}
+
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ pw.println(prefix + "SessionCreationRequest");
+
+ String indent = prefix + " ";
+
+ pw.println(indent + "mUniqueRequestId=" + mUniqueRequestId);
+ pw.println(indent + "mManagerRequestId=" + mManagerRequestId);
+ }
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 4806b52..4f0da795 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -387,6 +387,9 @@
userRecord.dump(pw, "");
}
}
+
+ pw.println();
+ mService2.dump(pw, "");
}
// Binder call
diff --git a/services/core/java/com/android/server/pm/AppsFilterBase.java b/services/core/java/com/android/server/pm/AppsFilterBase.java
index 14140b5..5fa5de7 100644
--- a/services/core/java/com/android/server/pm/AppsFilterBase.java
+++ b/services/core/java/com/android/server/pm/AppsFilterBase.java
@@ -28,7 +28,6 @@
import android.os.Binder;
import android.os.Handler;
import android.os.Process;
-import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -199,6 +198,7 @@
protected SnapshotCache<WatchedSparseBooleanMatrix> mShouldFilterCacheSnapshot;
protected volatile boolean mCacheReady = false;
+ protected volatile boolean mCacheEnabled = true;
protected static final boolean CACHE_VALID = true;
protected static final boolean CACHE_INVALID = false;
@@ -342,8 +342,7 @@
&& !isImplicitlyQueryable(callingAppId, targetPkgSetting.getAppId());
}
// use cache
- if (mCacheReady && SystemProperties.getBoolean("debug.pm.use_app_filter_cache",
- true)) {
+ if (mCacheReady && mCacheEnabled) {
if (!shouldFilterApplicationUsingCache(callingUid,
targetPkgSetting.getAppId(),
userId)) {
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index 79d72a3..4c21195 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -36,6 +36,7 @@
import android.content.pm.SigningDetails;
import android.content.pm.UserInfo;
import android.os.Handler;
+import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.DeviceConfig;
@@ -223,6 +224,12 @@
return new AppsFilterSnapshotImpl(AppsFilterImpl.this);
}
};
+ readCacheEnabledSysProp();
+ SystemProperties.addChangeCallback(this::readCacheEnabledSysProp);
+ }
+
+ private void readCacheEnabledSysProp() {
+ mCacheEnabled = SystemProperties.getBoolean("debug.pm.use_app_filter_cache", true);
}
/**
diff --git a/services/core/java/com/android/server/pm/AppsFilterSnapshotImpl.java b/services/core/java/com/android/server/pm/AppsFilterSnapshotImpl.java
index 019c853..4e268a2 100644
--- a/services/core/java/com/android/server/pm/AppsFilterSnapshotImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterSnapshotImpl.java
@@ -74,6 +74,7 @@
// cache is not ready, use an empty cache for the snapshot
mShouldFilterCache = new WatchedSparseBooleanMatrix();
}
+ mCacheEnabled = orig.mCacheEnabled;
mShouldFilterCacheSnapshot = new SnapshotCache.Sealed<>();
mBackgroundHandler = null;
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index 797d4c3..f6472a7 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -33,11 +33,9 @@
import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS;
import static com.android.server.pm.PackageManagerService.TAG;
import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_APK_IN_APEX;
-import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_FRAMEWORK_RES_SPLITS;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.pm.parsing.ApkLiteParseUtils;
import android.os.Environment;
import android.os.SystemClock;
import android.os.Trace;
@@ -121,29 +119,6 @@
mExecutorService = ParallelPackageParser.makeExecutorService();
}
- private List<File> getFrameworkResApkSplitFiles() {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanFrameworkResApkSplits");
- try {
- final List<File> splits = new ArrayList<>();
- final List<ApexManager.ActiveApexInfo> activeApexInfos =
- mPm.mApexManager.getActiveApexInfos();
- for (int i = 0; i < activeApexInfos.size(); i++) {
- ApexManager.ActiveApexInfo apexInfo = activeApexInfos.get(i);
- File splitsFolder = new File(apexInfo.apexDirectory, "etc/splits");
- if (splitsFolder.isDirectory()) {
- for (File file : splitsFolder.listFiles()) {
- if (ApkLiteParseUtils.isApkFile(file)) {
- splits.add(file);
- }
- }
- }
- }
- return splits;
- } finally {
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- }
- }
-
private List<ScanPartition> getSystemScanPartitions() {
final List<ScanPartition> scanPartitions = new ArrayList<>();
scanPartitions.addAll(mSystemPartitions);
@@ -270,7 +245,7 @@
long startTime) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
SystemClock.uptimeMillis());
- scanDirTracedLI(mPm.getAppInstallDir(), /* frameworkSplits= */ null, 0,
+ scanDirTracedLI(mPm.getAppInstallDir(), 0,
mScanFlags | SCAN_REQUIRE_KNOWN, packageParser, mExecutorService);
List<Runnable> unfinishedTasks = mExecutorService.shutdownNow();
@@ -338,15 +313,13 @@
if (partition.getOverlayFolder() == null) {
continue;
}
- scanDirTracedLI(partition.getOverlayFolder(), /* frameworkSplits= */ null,
+ scanDirTracedLI(partition.getOverlayFolder(),
mSystemParseFlags, mSystemScanFlags | partition.scanFlag,
packageParser, executorService);
}
- List<File> frameworkSplits = getFrameworkResApkSplitFiles();
- scanDirTracedLI(frameworkDir, frameworkSplits,
- mSystemParseFlags | PARSE_FRAMEWORK_RES_SPLITS,
- mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED,
+ scanDirTracedLI(frameworkDir,
+ mSystemParseFlags, mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED,
packageParser, executorService);
if (!mPm.mPackages.containsKey("android")) {
throw new IllegalStateException(
@@ -356,12 +329,12 @@
for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
final ScanPartition partition = mDirsToScanAsSystem.get(i);
if (partition.getPrivAppFolder() != null) {
- scanDirTracedLI(partition.getPrivAppFolder(), /* frameworkSplits= */ null,
+ scanDirTracedLI(partition.getPrivAppFolder(),
mSystemParseFlags,
mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag,
packageParser, executorService);
}
- scanDirTracedLI(partition.getAppFolder(), /* frameworkSplits= */ null,
+ scanDirTracedLI(partition.getAppFolder(),
mSystemParseFlags, mSystemScanFlags | partition.scanFlag,
packageParser, executorService);
}
@@ -379,7 +352,7 @@
}
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
- private void scanDirTracedLI(File scanDir, List<File> frameworkSplits,
+ private void scanDirTracedLI(File scanDir,
int parseFlags, int scanFlags,
PackageParser2 packageParser, ExecutorService executorService) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
@@ -388,7 +361,7 @@
// when scanning apk in apexes, we want to check the maxSdkVersion
parseFlags |= PARSE_APK_IN_APEX;
}
- mInstallPackageHelper.installPackagesFromDir(scanDir, frameworkSplits, parseFlags,
+ mInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags,
scanFlags, packageParser, executorService);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index f6e5b69..1746d93 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -3472,7 +3472,7 @@
}
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
- public void installPackagesFromDir(File scanDir, List<File> frameworkSplits, int parseFlags,
+ public void installPackagesFromDir(File scanDir, int parseFlags,
int scanFlags, PackageParser2 packageParser,
ExecutorService executorService) {
final File[] files = scanDir.listFiles();
@@ -3486,7 +3486,7 @@
+ " flags=0x" + Integer.toHexString(parseFlags));
}
ParallelPackageParser parallelPackageParser =
- new ParallelPackageParser(packageParser, executorService, frameworkSplits);
+ new ParallelPackageParser(packageParser, executorService);
// Submit files for parsing in parallel
int fileCount = 0;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 09bc25b..9481f8a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6541,7 +6541,7 @@
if (dependentState == null) {
continue;
}
- if (!Objects.equals(dependentState.getUserStateOrDefault(userId)
+ if (canSetOverlayPaths(dependentState.getUserStateOrDefault(userId)
.getSharedLibraryOverlayPaths()
.get(libName), newOverlayPaths)) {
String dependentPackageName = dependent.getPackageName();
@@ -6565,7 +6565,10 @@
}
}
- outUpdatedPackageNames.add(targetPackageName);
+ if (canSetOverlayPaths(packageState.getUserStateOrDefault(userId).getOverlayPaths(),
+ newOverlayPaths)) {
+ outUpdatedPackageNames.add(targetPackageName);
+ }
}
commitPackageStateMutation(null, mutator -> {
@@ -6616,6 +6619,17 @@
invalidatePackageInfoCache();
}
+ private boolean canSetOverlayPaths(OverlayPaths origPaths, OverlayPaths newPaths) {
+ if (Objects.equals(origPaths, newPaths)) {
+ return false;
+ }
+ if ((origPaths == null && newPaths.isEmpty())
+ || (newPaths == null && origPaths.isEmpty())) {
+ return false;
+ }
+ return true;
+ }
+
private void maybeUpdateSystemOverlays(String targetPackageName, OverlayPaths newOverlayPaths) {
if (!mResolverReplaced) {
if (targetPackageName.equals("android")) {
diff --git a/services/core/java/com/android/server/pm/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java
index 45030bf..5625884 100644
--- a/services/core/java/com/android/server/pm/ParallelPackageParser.java
+++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java
@@ -27,7 +27,6 @@
import com.android.server.pm.parsing.pkg.ParsedPackage;
import java.io.File;
-import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
@@ -55,17 +54,9 @@
private final ExecutorService mExecutorService;
- private final List<File> mFrameworkSplits;
-
ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService) {
- this(packageParser, executorService, /* frameworkSplits= */ null);
- }
-
- ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService,
- List<File> frameworkSplits) {
mPackageParser = packageParser;
mExecutorService = executorService;
- mFrameworkSplits = frameworkSplits;
}
static class ParseResult {
@@ -134,6 +125,6 @@
@VisibleForTesting
protected ParsedPackage parsePackage(File scanFile, int parseFlags)
throws PackageManagerException {
- return mPackageParser.parsePackage(scanFile, parseFlags, true, mFrameworkSplits);
+ return mPackageParser.parsePackage(scanFile, parseFlags, true);
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 3b3e1db..c77459d 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2048,8 +2048,8 @@
}
}
- @Override
- public boolean isUserSwitcherEnabled(@UserIdInt int mUserId) {
+ @VisibleForTesting
+ boolean isUserSwitcherEnabled(@UserIdInt int mUserId) {
boolean multiUserSettingOn = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.USER_SWITCHER_ENABLED,
Resources.getSystem().getBoolean(com.android.internal
@@ -2062,6 +2062,33 @@
}
@Override
+ public boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable,
+ @UserIdInt int mUserId) {
+ if (!isUserSwitcherEnabled(mUserId)) {
+ return false;
+ }
+ // The feature is enabled. But is it worth showing?
+ return showEvenIfNotActionable
+ || !hasUserRestriction(UserManager.DISALLOW_ADD_USER, mUserId) // Can add new user
+ || areThereMultipleSwitchableUsers(); // There are switchable users
+ }
+
+ /** Returns true if there is more than one user that can be switched to. */
+ private boolean areThereMultipleSwitchableUsers() {
+ List<UserInfo> aliveUsers = getUsers(true, true, true);
+ boolean isAnyAliveUser = false;
+ for (UserInfo userInfo : aliveUsers) {
+ if (userInfo.supportsSwitchToByUser()) {
+ if (isAnyAliveUser) {
+ return true;
+ }
+ isAnyAliveUser = true;
+ }
+ }
+ return false;
+ }
+
+ @Override
public boolean isRestricted(@UserIdInt int userId) {
if (userId != UserHandle.getCallingUserId()) {
checkCreateUsersPermission("query isRestricted for user " + userId);
@@ -7072,4 +7099,5 @@
}
return mAmInternal;
}
+
}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
index 6caddaf..f5ba3f6 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
@@ -143,15 +143,6 @@
@AnyThread
public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageManagerException {
- return parsePackage(packageFile, flags, useCaches, /* frameworkSplits= */ null);
- }
-
- /**
- * TODO(b/135203078): Document new package parsing
- */
- @AnyThread
- public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches,
- List<File> frameworkSplits) throws PackageManagerException {
var files = packageFile.listFiles();
// Apk directory is directly nested under the current directory
if (ArrayUtils.size(files) == 1 && files[0].isDirectory()) {
@@ -167,8 +158,7 @@
long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
ParseInput input = mSharedResult.get().reset();
- ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags,
- frameworkSplits);
+ ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags);
if (result.isError()) {
throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
result.getException());
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 14f787e..a8d48ae 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -238,7 +238,6 @@
* of required system property within the overlay tag.
*/
public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7;
- public static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
public static final int PARSE_APK_IN_APEX = 1 << 9;
public static final int PARSE_CHATTY = 1 << 31;
@@ -257,7 +256,6 @@
PARSE_IS_SYSTEM_DIR,
PARSE_MUST_BE_APK,
PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY,
- PARSE_FRAMEWORK_RES_SPLITS
})
@Retention(RetentionPolicy.SOURCE)
public @interface ParseFlags {}
@@ -307,7 +305,7 @@
isCoreApp);
}
});
- var parseResult = parser.parsePackage(input, file, parseFlags, /* frameworkSplits= */ null);
+ var parseResult = parser.parsePackage(input, file, parseFlags);
if (parseResult.isError()) {
return input.error(parseResult);
}
@@ -356,14 +354,9 @@
* not check whether {@code packageFile} has changed since the last parse, it's up to callers to
* do so.
*/
- public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags,
- List<File> frameworkSplits) {
- if (((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0)
- && frameworkSplits.size() > 0
- && packageFile.getAbsolutePath().endsWith("/framework-res.apk")) {
- return parseClusterPackage(input, packageFile, frameworkSplits, flags);
- } else if (packageFile.isDirectory()) {
- return parseClusterPackage(input, packageFile, /* frameworkSplits= */null, flags);
+ public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags) {
+ if (packageFile.isDirectory()) {
+ return parseClusterPackage(input, packageFile, flags);
} else {
return parseMonolithicPackage(input, packageFile, flags);
}
@@ -375,28 +368,17 @@
* identical package name and version codes, a single base APK, and unique
* split names.
* <p>
- * Can also be passed the framework-res.apk file and a list of split apks coming from apexes
- * (via {@code frameworkSplits}) in which case they will be parsed similar to cluster packages
- * even if they are in different folders. Note that this code path may have other behaviour
- * differences.
- * <p>
* Note that this <em>does not</em> perform signature verification; that must be done separately
* in {@link #getSigningDetails(ParseInput, ParsedPackage, boolean)}.
*/
private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir,
- List<File> frameworkSplits, int flags) {
- // parseClusterPackageLite should receive no flags (0) for regular splits but we want to
- // pass the flags for framework splits
+ int flags) {
int liteParseFlags = 0;
- if ((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0) {
- liteParseFlags = flags;
- }
if ((flags & PARSE_APK_IN_APEX) != 0) {
liteParseFlags |= PARSE_APK_IN_APEX;
}
final ParseResult<PackageLite> liteResult =
- ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, frameworkSplits,
- liteParseFlags);
+ ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, liteParseFlags);
if (liteResult.isError()) {
return input.error(liteResult);
}
@@ -647,7 +629,7 @@
final TypedArray manifestArray = res.obtainAttributes(parser, R.styleable.AndroidManifest);
try {
final boolean isCoreApp = parser.getAttributeBooleanValue(null /*namespace*/,
- "coreApp",false);
+ "coreApp", false);
final ParsingPackage pkg = mCallback.startParsingPackage(
pkgName, apkPath, codePath, manifestArray, isCoreApp);
final ParseResult<ParsingPackage> result =
diff --git a/services/core/java/com/android/server/resources/ResourcesManagerService.java b/services/core/java/com/android/server/resources/ResourcesManagerService.java
index cc27546..eec3a02 100644
--- a/services/core/java/com/android/server/resources/ResourcesManagerService.java
+++ b/services/core/java/com/android/server/resources/ResourcesManagerService.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.IResourcesManager;
+import android.content.res.ResourceTimer;
import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
@@ -55,7 +56,7 @@
@Override
public void onStart() {
- // Intentionally left empty.
+ ResourceTimer.start();
}
private final IBinder mService = new IResourcesManager.Stub() {
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index e21feae..886e8e6 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -93,6 +93,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
@@ -254,6 +255,7 @@
return;
}
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ checkNewAgents();
mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
mReceiver.register(mContext);
mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
@@ -262,7 +264,7 @@
refreshAgentList(UserHandle.USER_ALL);
refreshDeviceLockedForUser(UserHandle.USER_ALL);
} else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
- maybeEnableFactoryTrustAgents(mLockPatternUtils, UserHandle.USER_SYSTEM);
+ maybeEnableFactoryTrustAgents(UserHandle.USER_SYSTEM);
}
}
@@ -685,7 +687,7 @@
*/
public void lockUser(int userId) {
mLockPatternUtils.requireStrongAuth(
- StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, userId);
+ StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, userId);
try {
WindowManagerGlobal.getWindowManagerService().lockNow(null);
} catch (RemoteException e) {
@@ -1083,7 +1085,7 @@
return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
}
- private void maybeEnableFactoryTrustAgents(LockPatternUtils utils, int userId) {
+ private void maybeEnableFactoryTrustAgents(int userId) {
if (0 != Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.TRUST_AGENTS_INITIALIZED, 0, userId)) {
return;
@@ -1100,8 +1102,7 @@
} else { // A default agent is not set; perform regular trust agent discovery
for (ResolveInfo resolveInfo : resolveInfos) {
ComponentName componentName = getComponentName(resolveInfo);
- int applicationInfoFlags = resolveInfo.serviceInfo.applicationInfo.flags;
- if ((applicationInfoFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ if (!isSystemTrustAgent(resolveInfo)) {
Log.i(TAG, "Leaving agent " + componentName + " disabled because package "
+ "is not a system package.");
continue;
@@ -1110,13 +1111,88 @@
}
}
- List<ComponentName> previouslyEnabledAgents = utils.getEnabledTrustAgents(userId);
- discoveredAgents.addAll(previouslyEnabledAgents);
- utils.setEnabledTrustAgents(discoveredAgents, userId);
+ enableNewAgents(discoveredAgents, userId);
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, userId);
}
+ private void checkNewAgents() {
+ for (UserInfo userInfo : mUserManager.getAliveUsers()) {
+ checkNewAgentsForUser(userInfo.id);
+ }
+ }
+
+ /**
+ * Checks for any new trust agents that become available after the first boot, add them to the
+ * list of known agents, and enable them if they should be enabled by default.
+ */
+ private void checkNewAgentsForUser(int userId) {
+ // When KNOWN_TRUST_AGENTS_INITIALIZED is not set, only update the known agent list but do
+ // not enable any agent.
+ // These agents will be enabled by #maybeEnableFactoryTrustAgents if this is the first time
+ // that this device boots and TRUST_AGENTS_INITIALIZED is not already set.
+ // Otherwise, these agents may have been manually disabled by the user, and we should not
+ // re-enable them.
+ if (0 == Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, 0, userId)) {
+ initializeKnownAgents(userId);
+ return;
+ }
+
+ List<ComponentName> knownAgents = mLockPatternUtils.getKnownTrustAgents(userId);
+ List<ResolveInfo> agentInfoList = resolveAllowedTrustAgents(mContext.getPackageManager(),
+ userId);
+ ArraySet<ComponentName> newAgents = new ArraySet<>(agentInfoList.size());
+ ArraySet<ComponentName> newSystemAgents = new ArraySet<>(agentInfoList.size());
+
+ for (ResolveInfo agentInfo : agentInfoList) {
+ ComponentName agentComponentName = getComponentName(agentInfo);
+ if (knownAgents.contains(agentComponentName)) {
+ continue;
+ }
+ newAgents.add(agentComponentName);
+ if (isSystemTrustAgent(agentInfo)) {
+ newSystemAgents.add(agentComponentName);
+ }
+ }
+
+ if (newAgents.isEmpty()) {
+ return;
+ }
+
+ ArraySet<ComponentName> updatedKnowAgents = new ArraySet<>(knownAgents);
+ updatedKnowAgents.addAll(newAgents);
+ mLockPatternUtils.setKnownTrustAgents(updatedKnowAgents, userId);
+
+ // Do not auto enable new trust agents when the default agent is set
+ boolean hasDefaultAgent = getDefaultFactoryTrustAgent(mContext) != null;
+ if (!hasDefaultAgent) {
+ enableNewAgents(newSystemAgents, userId);
+ }
+ }
+
+ private void enableNewAgents(Collection<ComponentName> agents, int userId) {
+ if (agents.isEmpty()) {
+ return;
+ }
+
+ ArraySet<ComponentName> agentsToEnable = new ArraySet<>(agents);
+ agentsToEnable.addAll(mLockPatternUtils.getEnabledTrustAgents(userId));
+ mLockPatternUtils.setEnabledTrustAgents(agentsToEnable, userId);
+ }
+
+ private void initializeKnownAgents(int userId) {
+ List<ResolveInfo> agentInfoList = resolveAllowedTrustAgents(mContext.getPackageManager(),
+ userId);
+ ArraySet<ComponentName> agentComponentNames = new ArraySet<>(agentInfoList.size());
+ for (ResolveInfo agentInfo : agentInfoList) {
+ agentComponentNames.add(getComponentName(agentInfo));
+ }
+ mLockPatternUtils.setKnownTrustAgents(agentComponentNames, userId);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, 1, userId);
+ }
+
/**
* Returns the {@link ComponentName} for the default trust agent, or {@code null} if there
* is no trust agent set.
@@ -1152,6 +1228,10 @@
return allowedAgents;
}
+ private static boolean isSystemTrustAgent(ResolveInfo agentInfo) {
+ return (agentInfo.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ }
+
// Agent dispatch and aggregation
private boolean aggregateIsTrusted(int userId) {
@@ -1825,7 +1905,13 @@
}
@Override
+ public void onPackageAdded(String packageName, int uid) {
+ checkNewAgentsForUser(UserHandle.getUserId(uid));
+ }
+
+ @Override
public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ checkNewAgentsForUser(UserHandle.getUserId(uid));
// We're interested in all changes, even if just some components get enabled / disabled.
return true;
}
@@ -1860,7 +1946,7 @@
action)) {
int userId = getUserId(intent);
if (userId > 0) {
- maybeEnableFactoryTrustAgents(mLockPatternUtils, userId);
+ maybeEnableFactoryTrustAgents(userId);
}
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
int userId = getUserId(intent);
@@ -1997,7 +2083,7 @@
if (mStrongAuthTracker.isTrustAllowedForUser(mUserId)) {
if (DEBUG) Slog.d(TAG, "Revoking all trust because of trust timeout");
mLockPatternUtils.requireStrongAuth(
- mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, mUserId);
+ mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, mUserId);
}
maybeLockScreen(mUserId);
}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 7067ae4..08d2e69 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -237,9 +237,21 @@
if (mAssociatedTransitionInfo == null) {
launchResult = ":failed";
} else {
- launchResult = (abort ? ":canceled:" : mAssociatedTransitionInfo.mProcessSwitch
- ? ":completed:" : ":completed-same-process:")
- + mAssociatedTransitionInfo.mLastLaunchedActivity.packageName;
+ final String status;
+ if (abort) {
+ status = ":canceled:";
+ } else if (!mAssociatedTransitionInfo.mProcessSwitch) {
+ status = ":completed-same-process:";
+ } else {
+ if (endInfo.mTransitionType == TYPE_TRANSITION_HOT_LAUNCH) {
+ status = ":completed-hot:";
+ } else if (endInfo.mTransitionType == TYPE_TRANSITION_WARM_LAUNCH) {
+ status = ":completed-warm:";
+ } else {
+ status = ":completed-cold:";
+ }
+ }
+ launchResult = status + mAssociatedTransitionInfo.mLastLaunchedActivity.packageName;
}
// Put a supplement trace as the description of the async trace with the same id.
Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, mTraceName + launchResult);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 98d3adc..d8d75ed 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1586,11 +1586,19 @@
if (oldParent != null) {
oldParent.cleanUpActivityReferences(this);
+ // Update isVisibleRequested value of parent TaskFragment and send the callback to the
+ // client side if needed.
+ oldParent.onActivityVisibleRequestedChanged();
}
- if (newParent != null && isState(RESUMED)) {
- newParent.setResumedActivity(this, "onParentChanged");
- mImeInsetsFrozenUntilStartInput = false;
+ if (newParent != null) {
+ // Update isVisibleRequested value of parent TaskFragment and send the callback to the
+ // client side if needed.
+ newParent.onActivityVisibleRequestedChanged();
+ if (isState(RESUMED)) {
+ newParent.setResumedActivity(this, "onParentChanged");
+ mImeInsetsFrozenUntilStartInput = false;
+ }
}
if (rootTask != null && rootTask.topRunningActivity() == this) {
@@ -5094,6 +5102,10 @@
return;
}
mVisibleRequested = visible;
+ final TaskFragment taskFragment = getTaskFragment();
+ if (taskFragment != null) {
+ taskFragment.onActivityVisibleRequestedChanged();
+ }
setInsetsFrozen(!visible);
if (app != null) {
mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
@@ -9550,7 +9562,7 @@
@Override
boolean showToCurrentUser() {
- return mShowForAllUsers || mWmService.isCurrentProfile(mUserId);
+ return mShowForAllUsers || mWmService.isUserVisible(mUserId);
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index bc1c6b2..a0a4f76 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -892,9 +892,10 @@
final int userId = aInfo != null && aInfo.applicationInfo != null
? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
+ final int launchMode = aInfo != null ? aInfo.launchMode : 0;
if (err == ActivityManager.START_SUCCESS) {
Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false)
- + "} from uid " + callingUid);
+ + "} with " + launchModeToString(launchMode) + " from uid " + callingUid);
}
ActivityRecord sourceRecord = null;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 28cd001..5bddae6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1183,10 +1183,14 @@
}
if (!displayContent.isPrivate()) {
- // Anyone can launch on a public display.
- ProtoLog.d(WM_DEBUG_TASKS, "Launch on display check: allow launch on public "
- + "display");
- return true;
+ // Checks if the caller can be shown in the given public display.
+ int userId = UserHandle.getUserId(callingUid);
+ int displayId = display.getDisplayId();
+ boolean allowed = mWindowManager.mUmInternal.isUserVisible(userId, displayId);
+ ProtoLog.d(WM_DEBUG_TASKS,
+ "Launch on display check: %s launch for userId=%d on displayId=%d",
+ (allowed ? "allow" : "disallow"), userId, displayId);
+ return allowed;
}
// Check if the caller is the owner of the display.
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index dcc16eb..d54f77a 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -19,17 +19,16 @@
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
-import static com.android.server.wm.WindowContainerProto.IDENTIFIER;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowStateProto.IDENTIFIER;
import android.annotation.Nullable;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.util.ArrayMap;
-import android.util.proto.ProtoOutputStream;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import android.view.IWindow;
import android.view.InputApplicationHandle;
import android.view.InputChannel;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 837045c..829f01e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -186,6 +186,7 @@
import android.window.ITaskOrganizer;
import android.window.PictureInPictureSurfaceTransaction;
import android.window.StartingWindowInfo;
+import android.window.TaskFragmentParentInfo;
import android.window.TaskSnapshot;
import android.window.WindowContainerToken;
@@ -2679,6 +2680,7 @@
if (isRootTask()) {
updateSurfaceBounds();
}
+ sendTaskFragmentParentInfoChangedIfNeeded();
}
boolean isResizeable() {
@@ -2914,7 +2916,7 @@
@Override
boolean showToCurrentUser() {
return mForceShowForAllUsers || showForAllUsers()
- || mWmService.isCurrentProfile(getTopMostTask().mUserId);
+ || mWmService.isUserVisible(getTopMostTask().mUserId);
}
void setForceShowForAllUsers(boolean forceShowForAllUsers) {
@@ -3513,6 +3515,33 @@
return info;
}
+ /**
+ * Returns the {@link TaskFragmentParentInfo} which will send to the client
+ * {@link android.window.TaskFragmentOrganizer}
+ */
+ TaskFragmentParentInfo getTaskFragmentParentInfo() {
+ return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(), isVisibleRequested());
+ }
+
+ @Override
+ void onActivityVisibleRequestedChanged() {
+ if (mVisibleRequested != isVisibleRequested()) {
+ sendTaskFragmentParentInfoChangedIfNeeded();
+ }
+ }
+
+ void sendTaskFragmentParentInfoChangedIfNeeded() {
+ if (!isLeafTask()) {
+ // Only send parent info changed event for leaf task.
+ return;
+ }
+ final TaskFragment childOrganizedTf =
+ getTaskFragment(TaskFragment::isOrganizedTaskFragment);
+ if (childOrganizedTf != null) {
+ childOrganizedTf.sendTaskFragmentParentInfoChanged();
+ }
+ }
+
boolean isTaskId(int taskId) {
return mTaskId == taskId;
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index de4c84c..2cfc563 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -298,6 +298,9 @@
final Point mLastSurfaceSize = new Point();
+ /** The latest updated value when there's a child {@link #onActivityVisibleRequestedChanged} */
+ boolean mVisibleRequested;
+
private final Rect mTmpBounds = new Rect();
private final Rect mTmpFullBounds = new Rect();
/** For calculating screenWidthDp and screenWidthDp, i.e. the area without the system bars. */
@@ -2382,6 +2385,14 @@
}
}
+ void sendTaskFragmentParentInfoChanged() {
+ final Task parentTask = getParent().asTask();
+ if (mTaskFragmentOrganizer != null && parentTask != null) {
+ mTaskFragmentOrganizerController
+ .onTaskFragmentParentInfoChanged(mTaskFragmentOrganizer, parentTask);
+ }
+ }
+
private void sendTaskFragmentAppeared() {
if (mTaskFragmentOrganizer != null) {
mTaskFragmentOrganizerController.onTaskFragmentAppeared(mTaskFragmentOrganizer, this);
@@ -2417,7 +2428,7 @@
mRemoteToken.toWindowContainerToken(),
getConfiguration(),
getNonFinishingActivityCount(),
- isVisible(),
+ isVisibleRequested(),
childActivities,
positionInParent,
mClearedTaskForReuse,
@@ -2669,6 +2680,18 @@
return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds();
}
+ void onActivityVisibleRequestedChanged() {
+ final boolean isVisibleRequested = isVisibleRequested();
+ if (mVisibleRequested == isVisibleRequested) {
+ return;
+ }
+ mVisibleRequested = isVisibleRequested;
+ final TaskFragment parentTf = getParent().asTaskFragment();
+ if (parentTf != null) {
+ parentTf.onActivityVisibleRequestedChanged();
+ }
+ }
+
String toFullString() {
final StringBuilder sb = new StringBuilder(128);
sb.append(this);
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 8c037a7..2d5c989 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -49,6 +49,7 @@
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskFragmentOrganizerController;
import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentParentInfo;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
@@ -118,10 +119,10 @@
private final Map<TaskFragment, Integer> mTaskFragmentTaskIds = new WeakHashMap<>();
/**
- * Map from {@link Task#mTaskId} to the last Task {@link Configuration} sent to the
+ * Map from {@link Task#mTaskId} to the last {@link TaskFragmentParentInfo} sent to the
* organizer.
*/
- private final SparseArray<Configuration> mLastSentTaskFragmentParentConfigs =
+ private final SparseArray<TaskFragmentParentInfo> mLastSentTaskFragmentParentInfos =
new SparseArray<>();
/**
@@ -225,7 +226,7 @@
taskId = mTaskFragmentTaskIds.remove(tf);
if (!mTaskFragmentTaskIds.containsValue(taskId)) {
// No more TaskFragment in the Task.
- mLastSentTaskFragmentParentConfigs.remove(taskId);
+ mLastSentTaskFragmentParentInfos.remove(taskId);
}
} else {
// This can happen if the appeared wasn't sent before remove.
@@ -260,25 +261,27 @@
}
@Nullable
- TaskFragmentTransaction.Change prepareTaskFragmentParentInfoChanged(
- @NonNull Task task) {
+ TaskFragmentTransaction.Change prepareTaskFragmentParentInfoChanged(@NonNull Task task) {
final int taskId = task.mTaskId;
// Check if the parent info is different from the last reported parent info.
- final Configuration taskConfig = task.getConfiguration();
- final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(taskId);
- if (configurationsAreEqualForOrganizer(taskConfig, lastParentConfig)
- && taskConfig.windowConfiguration.getWindowingMode()
- == lastParentConfig.windowConfiguration.getWindowingMode()) {
+ final TaskFragmentParentInfo parentInfo = task.getTaskFragmentParentInfo();
+ final TaskFragmentParentInfo lastParentInfo = mLastSentTaskFragmentParentInfos
+ .get(taskId);
+ final Configuration lastParentConfig = lastParentInfo != null
+ ? lastParentInfo.getConfiguration() : null;
+ if (parentInfo.equalsForTaskFragmentOrganizer(lastParentInfo)
+ && configurationsAreEqualForOrganizer(parentInfo.getConfiguration(),
+ lastParentConfig)) {
return null;
}
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"TaskFragment parent info changed name=%s parentTaskId=%d",
task.getName(), taskId);
- mLastSentTaskFragmentParentConfigs.put(taskId, new Configuration(taskConfig));
+ mLastSentTaskFragmentParentInfos.put(taskId, new TaskFragmentParentInfo(parentInfo));
return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED)
.setTaskId(taskId)
- .setTaskConfiguration(taskConfig);
+ .setTaskFragmentParentInfo(parentInfo);
}
@NonNull
@@ -646,6 +649,34 @@
.build());
}
+ void onTaskFragmentParentInfoChanged(@NonNull ITaskFragmentOrganizer organizer,
+ @NonNull Task task) {
+ validateAndGetState(organizer);
+ final PendingTaskFragmentEvent pendingEvent = getLastPendingParentInfoChangedEvent(
+ organizer, task);
+ if (pendingEvent == null) {
+ addPendingEvent(new PendingTaskFragmentEvent.Builder(
+ PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED, organizer)
+ .setTask(task)
+ .build());
+ }
+ }
+
+ @Nullable
+ private PendingTaskFragmentEvent getLastPendingParentInfoChangedEvent(
+ @NonNull ITaskFragmentOrganizer organizer, @NonNull Task task) {
+ final List<PendingTaskFragmentEvent> events = mPendingTaskFragmentEvents
+ .get(organizer.asBinder());
+ for (int i = events.size() - 1; i >= 0; i--) {
+ final PendingTaskFragmentEvent event = events.get(i);
+ if (task == event.mTask
+ && event.mEventType == PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED) {
+ return event;
+ }
+ }
+ return null;
+ }
+
private void addPendingEvent(@NonNull PendingTaskFragmentEvent event) {
mPendingTaskFragmentEvents.get(event.mTaskFragmentOrg.asBinder()).add(event);
}
@@ -848,7 +879,9 @@
}
private boolean shouldSendEventWhenTaskInvisible(@NonNull PendingTaskFragmentEvent event) {
- if (event.mEventType == PendingTaskFragmentEvent.EVENT_ERROR) {
+ if (event.mEventType == PendingTaskFragmentEvent.EVENT_ERROR
+ // Always send parent info changed to update task visibility
+ || event.mEventType == PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED) {
return true;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 723aa19..93d9b0e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -42,6 +42,7 @@
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
@@ -1860,14 +1861,12 @@
// Whether this is in a Task with embedded activity.
flags |= FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
}
- final Rect taskBounds = parentTask.getBounds();
- final Rect startBounds = mAbsoluteBounds;
- final Rect endBounds = wc.getBounds();
- if (taskBounds.width() == startBounds.width()
- && taskBounds.height() == startBounds.height()
- && taskBounds.width() == endBounds.width()
- && taskBounds.height() == endBounds.height()) {
- // Whether the container fills the Task bounds before and after the transition.
+ if (parentTask.forAllActivities(ActivityRecord::hasStartingWindow)) {
+ // The starting window should cover all windows inside the leaf Task.
+ flags |= FLAG_IS_BEHIND_STARTING_WINDOW;
+ }
+ if (isWindowFillingTask(wc, parentTask)) {
+ // Whether the container fills its parent Task bounds.
flags |= FLAG_FILLS_TASK;
}
}
@@ -1889,6 +1888,22 @@
}
return flags;
}
+
+ /** Whether the container fills its parent Task bounds before and after the transition. */
+ private boolean isWindowFillingTask(@NonNull WindowContainer wc, @NonNull Task parentTask) {
+ final Rect taskBounds = parentTask.getBounds();
+ final int taskWidth = taskBounds.width();
+ final int taskHeight = taskBounds.height();
+ final Rect startBounds = mAbsoluteBounds;
+ final Rect endBounds = wc.getBounds();
+ // Treat it as filling the task if it is not visible.
+ final boolean isInvisibleOrFillingTaskBeforeTransition = !mVisible
+ || (taskWidth == startBounds.width() && taskHeight == startBounds.height());
+ final boolean isInVisibleOrFillingTaskAfterTransition = !wc.isVisibleRequested()
+ || (taskWidth == endBounds.width() && taskHeight == endBounds.height());
+ return isInvisibleOrFillingTaskBeforeTransition
+ && isInVisibleOrFillingTaskAfterTransition;
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 8cc0d5d..69d86b6 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2195,6 +2195,17 @@
callback, boundary, includeBoundary, traverseTopToBottom, boundaryFound);
}
+ @Nullable
+ TaskFragment getTaskFragment(Predicate<TaskFragment> callback) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final TaskFragment tf = mChildren.get(i).getTaskFragment(callback);
+ if (tf != null) {
+ return tf;
+ }
+ }
+ return null;
+ }
+
WindowState getWindow(Predicate<WindowState> callback) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState w = mChildren.get(i).getWindow(callback);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 49e7a0c..43ef1d4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -123,7 +123,10 @@
import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
+import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
@@ -155,6 +158,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityThread;
@@ -315,6 +319,7 @@
import com.android.server.UiThread;
import com.android.server.Watchdog;
import com.android.server.input.InputManagerService;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
import com.android.server.power.ShutdownThread;
@@ -504,15 +509,9 @@
};
/**
- * Current user when multi-user is enabled. Don't show windows of
- * non-current user. Also see mCurrentProfileIds.
+ * Current user when multi-user is enabled. Don't show windows of non-current user.
*/
- int mCurrentUserId;
- /**
- * Users that are profiles of the current user. These are also allowed to show windows
- * on the current user.
- */
- int[] mCurrentProfileIds = new int[] {};
+ @UserIdInt int mCurrentUserId;
final Context mContext;
@@ -534,6 +533,7 @@
final IActivityManager mActivityManager;
final ActivityManagerInternal mAmInternal;
+ final UserManagerInternal mUmInternal;
final AppOpsManager mAppOps;
final PackageManagerInternal mPmInternal;
@@ -1263,6 +1263,7 @@
mActivityManager = ActivityManager.getService();
mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mUmInternal = LocalServices.getService(UserManagerInternal.class);
mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
AppOpsManager.OnOpChangedInternalListener opListener =
new AppOpsManager.OnOpChangedInternalListener() {
@@ -2604,10 +2605,22 @@
if (win.isWinVisibleLw() && win.mDisplayContent.okToAnimate()) {
String reason = null;
if (winAnimator.applyAnimationLocked(transit, false)) {
+ // This is a WMCore-driven window animation.
reason = "applyAnimation";
focusMayChange = true;
win.mAnimatingExit = true;
- } else if (win.isExitAnimationRunningSelfOrParent()) {
+ } else if (
+ // This is already animating via a WMCore-driven window animation
+ win.isSelfAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION)
+ // Or already animating as part of a legacy app-transition
+ || win.isAnimating(PARENTS | TRANSITION,
+ ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)
+ // Or already animating as part of a shell-transition.
+ || (win.inTransition()
+ // Filter out non-app windows since transitions don't animate those
+ // (but may still "wait" on them for readiness)
+ && (win.mActivityRecord != null || win.mIsWallpaper))) {
+ // TODO(b/247005789): set mAnimatingExit somewhere in shell-transitions setup.
reason = "animating";
win.mAnimatingExit = true;
} else if (win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)
@@ -3558,16 +3571,9 @@
confirm);
}
- public void setCurrentProfileIds(final int[] currentProfileIds) {
- synchronized (mGlobalLock) {
- mCurrentProfileIds = currentProfileIds;
- }
- }
-
- public void setCurrentUser(final int newUserId, final int[] currentProfileIds) {
+ public void setCurrentUser(@UserIdInt int newUserId) {
synchronized (mGlobalLock) {
mCurrentUserId = newUserId;
- mCurrentProfileIds = currentProfileIds;
mPolicy.setCurrentUserLw(newUserId);
mKeyguardDisableHandler.setCurrentUser(newUserId);
@@ -3590,12 +3596,8 @@
}
/* Called by WindowState */
- boolean isCurrentProfile(int userId) {
- if (userId == mCurrentUserId) return true;
- for (int i = 0; i < mCurrentProfileIds.length; i++) {
- if (mCurrentProfileIds[i] == userId) return true;
- }
- return false;
+ boolean isUserVisible(@UserIdInt int userId) {
+ return mUmInternal.isUserVisible(userId);
}
public void enableScreenAfterBoot() {
@@ -9277,46 +9279,4 @@
"Unexpected letterbox background type: " + letterboxBackgroundType);
}
}
-
- @Override
- public void captureDisplay(int displayId, @Nullable ScreenCapture.CaptureArgs captureArgs,
- ScreenCapture.ScreenCaptureListener listener) {
- Slog.d(TAG, "captureDisplay");
- if (!checkCallingPermission(READ_FRAME_BUFFER, "captureDisplay()")) {
- throw new SecurityException("Requires READ_FRAME_BUFFER permission");
- }
-
- ScreenCapture.captureLayers(getCaptureArgs(displayId, captureArgs), listener);
- }
-
- @VisibleForTesting
- ScreenCapture.LayerCaptureArgs getCaptureArgs(int displayId,
- @Nullable ScreenCapture.CaptureArgs captureArgs) {
- final SurfaceControl displaySurfaceControl;
- synchronized (mGlobalLock) {
- DisplayContent displayContent = mRoot.getDisplayContent(displayId);
- if (displayContent == null) {
- throw new IllegalArgumentException("Trying to screenshot and invalid display: "
- + displayId);
- }
-
- displaySurfaceControl = displayContent.getSurfaceControl();
-
- if (captureArgs == null) {
- captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
- .build();
- }
-
- if (captureArgs.mSourceCrop.isEmpty()) {
- displayContent.getBounds(mTmpRect);
- mTmpRect.offsetTo(0, 0);
- } else {
- mTmpRect.set(captureArgs.mSourceCrop);
- }
- }
-
- return new ScreenCapture.LayerCaptureArgs.Builder(displaySurfaceControl, captureArgs)
- .setSourceCrop(mTmpRect)
- .build();
- }
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index df7b8bb..1d43d18 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3684,7 +3684,7 @@
}
return win.showForAllUsers()
- || mWmService.isCurrentProfile(win.mShowUserId);
+ || mWmService.isUserVisible(win.mShowUserId);
}
private static void applyInsets(Region outRegion, Rect frame, Rect inset) {
diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp
index 61f2b14..02e5061 100644
--- a/services/core/jni/com_android_server_display_DisplayControl.cpp
+++ b/services/core/jni/com_android_server_display_DisplayControl.cpp
@@ -17,6 +17,7 @@
#include <android_util_Binder.h>
#include <gui/SurfaceComposerClient.h>
#include <jni.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedUtfChars.h>
namespace android {
@@ -33,6 +34,27 @@
SurfaceComposerClient::destroyDisplay(token);
}
+static void nativeOverrideHdrTypes(JNIEnv* env, jclass clazz, jobject tokenObject,
+ jintArray jHdrTypes) {
+ sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
+ if (token == nullptr || jHdrTypes == nullptr) return;
+
+ ScopedIntArrayRO hdrTypes(env, jHdrTypes);
+ size_t numHdrTypes = hdrTypes.size();
+
+ std::vector<ui::Hdr> hdrTypesVector;
+ hdrTypesVector.reserve(numHdrTypes);
+ for (int i = 0; i < numHdrTypes; i++) {
+ hdrTypesVector.push_back(static_cast<ui::Hdr>(hdrTypes[i]));
+ }
+
+ status_t error = SurfaceComposerClient::overrideHdrTypes(token, hdrTypesVector);
+ if (error != NO_ERROR) {
+ jniThrowExceptionFmt(env, "java/lang/SecurityException",
+ "ACCESS_SURFACE_FLINGER is missing");
+ }
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod sDisplayMethods[] = {
@@ -41,6 +63,8 @@
(void*)nativeCreateDisplay },
{"nativeDestroyDisplay", "(Landroid/os/IBinder;)V",
(void*)nativeDestroyDisplay },
+ {"nativeOverrideHdrTypes", "(Landroid/os/IBinder;[I)V",
+ (void*)nativeOverrideHdrTypes },
// clang-format on
};
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index bb6bd4a..42dfee3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5725,7 +5725,7 @@
try {
return setKeyChainGrantInternal(alias, hasGrant, granteeUid, caller.getUserHandle());
} catch (IllegalArgumentException e) {
- if (mInjector.isChangeEnabled(THROW_EXCEPTION_WHEN_KEY_MISSING, packageName,
+ if (mInjector.isChangeEnabled(THROW_EXCEPTION_WHEN_KEY_MISSING, callerPackage,
caller.getUserId())) {
throw e;
}
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index 07b763d..33ac735 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -34,10 +34,15 @@
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<uses-permission android:name="android.permission.MANAGE_GAME_ACTIVITY" />
<uses-permission android:name="android.permission.SET_ALWAYS_FINISH" />
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
+ <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
<!-- needed by MasterClearReceiverTest to display a system dialog -->
<uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
+ <!-- needed by TrustManagerServiceTest to access LockSettings' secure storage -->
+ <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
+
<application android:testOnly="true"
android:debuggable="true">
<uses-library android:name="android.test.runner" />
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 9bdc93e..6bf102a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -16,7 +16,6 @@
package com.android.server.am;
-import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -46,7 +45,6 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
@@ -92,7 +90,8 @@
private final Impl mImpl;
private enum Impl {
- DEFAULT
+ DEFAULT,
+ MODERN,
}
private Context mContext;
@@ -116,7 +115,7 @@
@Parameters(name = "impl={0}")
public static Collection<Object[]> data() {
- return Arrays.asList(new Object[][] { {Impl.DEFAULT} });
+ return Arrays.asList(new Object[][] { {Impl.DEFAULT}, {Impl.MODERN} });
}
public BroadcastQueueTest(Impl impl) {
@@ -152,7 +151,9 @@
final ApplicationInfo ai = invocation.getArgument(1);
final ProcessRecord res = makeActiveProcessRecord(ai, processName);
mHandlerThread.getThreadHandler().post(() -> {
- mQueue.onApplicationAttachedLocked(res);
+ synchronized (mAms) {
+ mQueue.onApplicationAttachedLocked(res);
+ }
});
return res;
}).when(mAms).startProcessLocked(any(), any(), anyBoolean(), anyInt(),
@@ -177,6 +178,9 @@
mQueue = new BroadcastQueueImpl(mAms, mHandlerThread.getThreadHandler(), TAG,
constants, emptySkipPolicy, emptyHistory, false,
ProcessList.SCHED_GROUP_DEFAULT);
+ } else if (mImpl == Impl.MODERN) {
+ mQueue = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
+ constants, emptySkipPolicy, emptyHistory);
} else {
throw new UnsupportedOperationException();
}
@@ -230,8 +234,10 @@
Log.v(TAG, "Intercepting scheduleReceiver() for "
+ Arrays.toString(invocation.getArguments()));
mHandlerThread.getThreadHandler().post(() -> {
- mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
- null, null, false, false);
+ synchronized (mAms) {
+ mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
+ null, null, false, false);
+ }
});
return null;
}).when(thread).scheduleReceiver(any(), any(), any(), anyInt(), any(), any(), anyBoolean(),
@@ -240,10 +246,15 @@
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for "
+ Arrays.toString(invocation.getArguments()));
- mHandlerThread.getThreadHandler().post(() -> {
- mQueue.finishReceiverLocked(r, Activity.RESULT_OK, null, null,
- false, false);
- });
+ final boolean ordered = invocation.getArgument(5);
+ if (ordered) {
+ mHandlerThread.getThreadHandler().post(() -> {
+ synchronized (mAms) {
+ mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
+ null, null, false, false);
+ }
+ });
+ }
return null;
}).when(thread).scheduleRegisteredReceiver(any(), any(), anyInt(), any(), any(),
anyBoolean(), anyBoolean(), anyInt(), anyInt());
@@ -302,6 +313,12 @@
};
}
+ private void enqueueBroadcast(BroadcastRecord r) {
+ synchronized (mAms) {
+ mQueue.enqueueBroadcastLocked(r);
+ }
+ }
+
private void waitForIdle() throws Exception {
mQueue.waitForIdle(null);
}
@@ -349,7 +366,7 @@
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
final Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
- mQueue.enqueueBroadcastLocked(makeBroadcastRecord(intent, callerApp,
+ enqueueBroadcast(makeBroadcastRecord(intent, callerApp,
List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
waitForIdle();
@@ -368,12 +385,12 @@
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
- mQueue.enqueueBroadcastLocked(makeBroadcastRecord(timezone, callerApp,
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- mQueue.enqueueBroadcastLocked(makeBroadcastRecord(airplane, callerApp,
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
waitForIdle();
@@ -394,12 +411,12 @@
// the second time it should already be running
final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
- mQueue.enqueueBroadcastLocked(makeBroadcastRecord(timezone, callerApp,
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- mQueue.enqueueBroadcastLocked(makeBroadcastRecord(airplane, callerApp,
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
waitForIdle();
@@ -407,7 +424,6 @@
getUidForPackage(PACKAGE_GREEN));
final ProcessRecord receiverBlueApp = mAms.getProcessRecordLocked(PACKAGE_BLUE,
getUidForPackage(PACKAGE_BLUE));
- assertTrue(receiverBlueApp.getPid() > receiverGreenApp.getPid());
verifyScheduleReceiver(receiverGreenApp, timezone);
verifyScheduleReceiver(receiverGreenApp, airplane);
verifyScheduleReceiver(receiverBlueApp, timezone);
@@ -423,7 +439,7 @@
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
final Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
- mQueue.enqueueBroadcastLocked(makeBroadcastRecord(intent, callerApp,
+ enqueueBroadcast(makeBroadcastRecord(intent, callerApp,
List.of(makeRegisteredReceiver(receiverApp))));
waitForIdle();
@@ -442,12 +458,12 @@
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
- mQueue.enqueueBroadcastLocked(makeBroadcastRecord(timezone, callerApp,
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
List.of(makeRegisteredReceiver(receiverGreenApp),
makeRegisteredReceiver(receiverBlueApp))));
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- mQueue.enqueueBroadcastLocked(makeBroadcastRecord(airplane, callerApp,
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
List.of(makeRegisteredReceiver(receiverBlueApp))));
waitForIdle();
@@ -468,14 +484,14 @@
final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
- mQueue.enqueueBroadcastLocked(makeBroadcastRecord(timezone, callerApp,
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
makeRegisteredReceiver(receiverGreenApp),
makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
makeRegisteredReceiver(receiverYellowApp))));
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- mQueue.enqueueBroadcastLocked(makeBroadcastRecord(airplane, callerApp,
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
List.of(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
waitForIdle();
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
new file mode 100644
index 0000000..1a5d496
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+import android.util.FloatProperty;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.display.RampAnimator.DualRampAnimator;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPowerController2Test {
+ private static final String UNIQUE_DISPLAY_ID = "unique_id_test123";
+ private static final int DISPLAY_ID = 42;
+
+ private OffsettableClock mClock;
+ private TestLooper mTestLooper;
+ private Handler mHandler;
+ private DisplayPowerController2.Injector mInjector;
+ private Context mContextSpy;
+
+ @Mock
+ private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
+ @Mock
+ private SensorManager mSensorManagerMock;
+ @Mock
+ private DisplayBlanker mDisplayBlankerMock;
+ @Mock
+ private LogicalDisplay mLogicalDisplayMock;
+ @Mock
+ private DisplayDevice mDisplayDeviceMock;
+ @Mock
+ private BrightnessTracker mBrightnessTrackerMock;
+ @Mock
+ private BrightnessSetting mBrightnessSettingMock;
+ @Mock
+ private WindowManagerPolicy mWindowManagerPolicyMock;
+ @Mock
+ private PowerManager mPowerManagerMock;
+ @Mock
+ private Resources mResourcesMock;
+ @Mock
+ private DisplayDeviceConfig mDisplayDeviceConfigMock;
+ @Mock
+ private DisplayPowerState mDisplayPowerStateMock;
+ @Mock
+ private DualRampAnimator<DisplayPowerState> mDualRampAnimatorMock;
+
+ @Captor
+ private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+ mClock = new OffsettableClock.Stopped();
+ mTestLooper = new TestLooper(mClock::now);
+ mHandler = new Handler(mTestLooper.getLooper());
+ mInjector = new DisplayPowerController2.Injector() {
+ @Override
+ DisplayPowerController2.Clock getClock() {
+ return mClock::now;
+ }
+
+ @Override
+ DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
+ int displayId, int displayState) {
+ return mDisplayPowerStateMock;
+ }
+
+ @Override
+ DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
+ FloatProperty<DisplayPowerState> firstProperty,
+ FloatProperty<DisplayPowerState> secondProperty) {
+ return mDualRampAnimatorMock;
+ }
+ };
+
+ addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+
+ when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
+ when(mContextSpy.getResources()).thenReturn(mResourcesMock);
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+ }
+
+ @Test
+ public void testReleaseProxSuspendBlockersOnExit() throws Exception {
+ setUpDisplay(DISPLAY_ID, UNIQUE_DISPLAY_ID);
+
+ Sensor proxSensor = setUpProxSensor();
+
+ DisplayPowerController2 dpc = new DisplayPowerController2(
+ mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
+ mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
+ mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
+ });
+
+ when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON);
+ // send a display power request
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ dpr.useProximitySensor = true;
+ dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+ // Run updatePowerState to start listener for the prox sensor
+ advanceTime(1);
+
+ SensorEventListener listener = getSensorEventListener(proxSensor);
+ assertNotNull(listener);
+
+ listener.onSensorChanged(TestUtils.createSensorEvent(proxSensor, 5 /* lux */));
+ advanceTime(1);
+
+ // two times, one for unfinished business and one for proximity
+ verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
+ dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
+ verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
+ dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+
+ dpc.stop();
+ advanceTime(1);
+
+ // two times, one for unfinished business and one for proximity
+ verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
+ dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
+ verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
+ dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+ }
+
+ /**
+ * Creates a mock and registers it to {@link LocalServices}.
+ */
+ private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+ LocalServices.removeServiceForTest(clazz);
+ LocalServices.addService(clazz, mock);
+ }
+
+ private void advanceTime(long timeMs) {
+ mClock.fastForward(timeMs);
+ mTestLooper.dispatchAll();
+ }
+
+ private Sensor setUpProxSensor() throws Exception {
+ Sensor proxSensor = TestUtils.createSensor(
+ Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY);
+ when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
+ .thenReturn(List.of(proxSensor));
+ return proxSensor;
+ }
+
+ private SensorEventListener getSensorEventListener(Sensor sensor) {
+ verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
+ eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
+ return mSensorEventListenerCaptor.getValue();
+ }
+
+ private void setUpDisplay(int displayId, String uniqueId) {
+ DisplayInfo info = new DisplayInfo();
+ DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
+
+ when(mLogicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
+ when(mLogicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(mDisplayDeviceMock);
+ when(mLogicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
+ when(mLogicalDisplayMock.isEnabled()).thenReturn(true);
+ when(mLogicalDisplayMock.getPhase()).thenReturn(LogicalDisplay.DISPLAY_PHASE_ENABLED);
+ when(mDisplayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
+ when(mDisplayDeviceMock.getUniqueId()).thenReturn(uniqueId);
+ when(mDisplayDeviceMock.getDisplayDeviceConfig()).thenReturn(mDisplayDeviceConfigMock);
+ when(mDisplayDeviceConfigMock.getProximitySensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = Sensor.STRING_TYPE_PROXIMITY;
+ name = null;
+ }
+ });
+ when(mDisplayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
index bc9e9bb..941a3a4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
@@ -16,14 +16,18 @@
package com.android.server.display.color;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertArrayEquals;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.display.DisplayManagerInternal;
import android.os.Binder;
import android.os.IBinder;
import android.view.SurfaceControl;
@@ -50,6 +54,8 @@
private Context mMockedContext;
@Mock
private Resources mMockedResources;
+ @Mock
+ private DisplayManagerInternal mDisplayManagerInternal;
private MockitoSession mSession;
private Resources mResources;
@@ -81,7 +87,6 @@
doReturn(mMockedResources).when(mMockedContext).getResources();
mDisplayToken = new Binder();
- doReturn(mDisplayToken).when(() -> SurfaceControl.getInternalDisplayToken());
}
@After
@@ -114,8 +119,8 @@
displayPrimaries.white.X = 0.950456f;
displayPrimaries.white.Y = 1.000000f;
displayPrimaries.white.Z = 1.089058f;
- doReturn(displayPrimaries)
- .when(() -> SurfaceControl.getDisplayNativePrimaries(mDisplayToken));
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+ .thenReturn(displayPrimaries);
setUpTintController();
assertWithMessage("Setup with valid SurfaceControl failed")
@@ -134,8 +139,8 @@
displayPrimaries.green = new CieXyz();
displayPrimaries.blue = new CieXyz();
displayPrimaries.white = new CieXyz();
- doReturn(displayPrimaries)
- .when(() -> SurfaceControl.getDisplayNativePrimaries(mDisplayToken));
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+ .thenReturn(displayPrimaries);
setUpTintController();
assertWithMessage("Setup with invalid SurfaceControl succeeded")
@@ -154,7 +159,7 @@
.when(mMockedResources)
.getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
// Make SurfaceControl setup fail
- doReturn(null).when(() -> SurfaceControl.getDisplayNativePrimaries(mDisplayToken));
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
setUpTintController();
assertWithMessage("Setup with valid Resources failed")
@@ -178,7 +183,7 @@
.when(mMockedResources)
.getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
// Make SurfaceControl setup fail
- doReturn(null).when(() -> SurfaceControl.getDisplayNativePrimaries(mDisplayToken));
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
setUpTintController();
assertWithMessage("Setup with invalid Resources succeeded")
@@ -208,8 +213,8 @@
displayPrimaries.white.X = 0.950456f;
displayPrimaries.white.Y = 1.000000f;
displayPrimaries.white.Z = 1.089058f;
- doReturn(displayPrimaries)
- .when(() -> SurfaceControl.getDisplayNativePrimaries(mDisplayToken));
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+ .thenReturn(displayPrimaries);
setUpTintController();
assertWithMessage("Setup with valid SurfaceControl failed")
@@ -234,7 +239,8 @@
}
private void setUpTintController() {
- mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController();
+ mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
+ mDisplayManagerInternal);
mDisplayWhiteBalanceTintController.setUp(mMockedContext, true);
mDisplayWhiteBalanceTintController.setActivated(true);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakePackageResetHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakePackageResetHelper.java
new file mode 100644
index 0000000..c2768d51
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakePackageResetHelper.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.injector;
+
+/** Version of PackageResetHelper for testing. */
+public class FakePackageResetHelper extends PackageResetHelper {
+
+ public FakePackageResetHelper() {}
+
+ @Override
+ protected void onRegister() {}
+
+ @Override
+ protected void onUnregister() {}
+
+ public boolean isResetableForPackage(String packageName) {
+ return queryResetableForPackage(packageName);
+ }
+
+ public void reset(String packageName) {
+ notifyPackageReset(packageName);
+ }
+}
+
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
index 02cacb7..ca73091 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
@@ -35,6 +35,7 @@
private final FakeDeviceIdleHelper mDeviceIdleHelper;
private final FakeEmergencyHelper mEmergencyHelper;
private final LocationUsageLogger mLocationUsageLogger;
+ private final FakePackageResetHelper mPackageResetHelper;
public TestInjector(Context context) {
mUserInfoHelper = new FakeUserInfoHelper();
@@ -50,6 +51,7 @@
mDeviceIdleHelper = new FakeDeviceIdleHelper();
mEmergencyHelper = new FakeEmergencyHelper();
mLocationUsageLogger = new LocationUsageLogger();
+ mPackageResetHelper = new FakePackageResetHelper();
}
@Override
@@ -116,4 +118,9 @@
public LocationUsageLogger getLocationUsageLogger() {
return mLocationUsageLogger;
}
+
+ @Override
+ public FakePackageResetHelper getPackageResetHelper() {
+ return mPackageResetHelper;
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 0ac1443..20e4e80 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -1218,6 +1218,44 @@
assertThat(mProvider.getRequest().isActive()).isFalse();
}
+ @Test
+ public void testQueryPackageReset() {
+ assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isFalse();
+
+ ILocationListener listener1 = createMockLocationListener();
+ mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource(
+ WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener1);
+ assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue();
+
+ ILocationListener listener2 = createMockLocationListener();
+ mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource(
+ WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener2);
+ assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue();
+
+ mManager.unregisterLocationRequest(listener1);
+ assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue();
+
+ mManager.unregisterLocationRequest(listener2);
+ assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isFalse();
+ }
+
+ @Test
+ public void testPackageReset() {
+ ILocationListener listener1 = createMockLocationListener();
+ mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource(
+ WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener1);
+ ILocationListener listener2 = createMockLocationListener();
+ mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource(
+ WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener2);
+
+ assertThat(mProvider.getRequest().isActive()).isTrue();
+ assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue();
+
+ mInjector.getPackageResetHelper().reset("mypackage");
+ assertThat(mProvider.getRequest().isActive()).isFalse();
+ assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isFalse();
+ }
+
private ILocationListener createMockLocationListener() {
return spy(new ILocationListener.Stub() {
@Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index bb5b1d8..cc57b9f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -62,10 +62,10 @@
import com.android.server.extendedtestutils.wheneverStatic
import com.android.server.pm.dex.DexManager
import com.android.server.pm.parsing.PackageParser2
-import com.android.server.pm.pkg.AndroidPackage
import com.android.server.pm.parsing.pkg.PackageImpl
import com.android.server.pm.parsing.pkg.ParsedPackage
import com.android.server.pm.permission.PermissionManagerServiceInternal
+import com.android.server.pm.pkg.AndroidPackage
import com.android.server.pm.pkg.parsing.ParsingPackage
import com.android.server.pm.pkg.parsing.ParsingPackageUtils
import com.android.server.pm.resolution.ComponentResolver
@@ -77,14 +77,6 @@
import com.android.server.testutils.nullable
import com.android.server.testutils.whenever
import com.android.server.utils.WatchedArrayMap
-import libcore.util.HexEncoding
-import org.junit.Assert
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-import org.mockito.AdditionalMatchers.or
-import org.mockito.Mockito
-import org.mockito.quality.Strictness
import java.io.File
import java.io.IOException
import java.nio.file.Files
@@ -93,6 +85,14 @@
import java.util.Arrays
import java.util.Random
import java.util.concurrent.FutureTask
+import libcore.util.HexEncoding
+import org.junit.Assert
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import org.mockito.AdditionalMatchers.or
+import org.mockito.Mockito
+import org.mockito.quality.Strictness
/**
* A utility for mocking behavior of the system and dependencies when testing PackageManagerService
@@ -522,7 +522,7 @@
whenever(mocks.packageParser.parsePackage(
or(eq(path), eq(basePath)), anyInt(), anyBoolean())) { parsedPackage }
whenever(mocks.packageParser.parsePackage(
- or(eq(path), eq(basePath)), anyInt(), anyBoolean(), any())) { parsedPackage }
+ or(eq(path), eq(basePath)), anyInt(), anyBoolean())) { parsedPackage }
return parsedPackage
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
index 987192d..da929af 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
@@ -23,6 +23,7 @@
import android.util.Log
import com.android.server.pm.pkg.AndroidPackage
import com.android.server.testutils.whenever
+import java.io.File
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.notNullValue
@@ -33,13 +34,11 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.argThat
import org.mockito.Mockito
import org.mockito.Mockito.verify
-import java.io.File
@RunWith(JUnit4::class)
class PackageManagerServiceBootTest {
@@ -120,8 +119,7 @@
whenever(rule.mocks().packageParser.parsePackage(
argThat { path: File -> path.path.contains("a.data.package") },
anyInt(),
- anyBoolean(),
- any()))
+ anyBoolean()))
.thenThrow(PackageManagerException(
PackageManager.INSTALL_FAILED_INVALID_APK, "Oh no!"))
val pm = createPackageManagerService()
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
new file mode 100644
index 0000000..33870f1
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.trust;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.argThat;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.service.trust.TrustAgentService;
+import android.testing.TestableContext;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.SystemServiceManager;
+
+import com.google.android.collect.Lists;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class TrustManagerServiceTest {
+
+ @Rule
+ public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule
+ public final MockContext mMockContext = new MockContext(
+ ApplicationProvider.getApplicationContext());
+
+ private static final String URI_SCHEME_PACKAGE = "package";
+ private static final int TEST_USER_ID = UserHandle.USER_SYSTEM;
+
+ private final TestLooper mLooper = new TestLooper();
+ private final ArrayList<ResolveInfo> mTrustAgentResolveInfoList = new ArrayList<>();
+ private final LockPatternUtils mLockPatternUtils = new LockPatternUtils(mMockContext);
+ private final TrustManagerService mService = new TrustManagerService(mMockContext);
+
+ @Mock
+ private PackageManager mPackageManagerMock;
+
+ @Before
+ public void setUp() {
+ resetTrustAgentLockSettings();
+ LocalServices.addService(SystemServiceManager.class, mock(SystemServiceManager.class));
+
+ ArgumentMatcher<Intent> trustAgentIntentMatcher = new ArgumentMatcher<Intent>() {
+ @Override
+ public boolean matches(Intent argument) {
+ return TrustAgentService.SERVICE_INTERFACE.equals(argument.getAction());
+ }
+ };
+ when(mPackageManagerMock.queryIntentServicesAsUser(argThat(trustAgentIntentMatcher),
+ anyInt(), anyInt())).thenReturn(mTrustAgentResolveInfoList);
+ when(mPackageManagerMock.checkPermission(any(), any())).thenReturn(
+ PackageManager.PERMISSION_GRANTED);
+ mMockContext.setMockPackageManager(mPackageManagerMock);
+ }
+
+ @After
+ public void tearDown() {
+ resetTrustAgentLockSettings();
+ LocalServices.removeServiceForTest(SystemServiceManager.class);
+ }
+
+ @Test
+ public void firstBootCompleted_systemTrustAgentsEnabled() {
+ ComponentName systemTrustAgent1 = ComponentName.unflattenFromString(
+ "com.android/.SystemTrustAgent");
+ ComponentName systemTrustAgent2 = ComponentName.unflattenFromString(
+ "com.android/.AnotherSystemTrustAgent");
+ ComponentName userTrustAgent1 = ComponentName.unflattenFromString(
+ "com.user/.UserTrustAgent");
+ ComponentName userTrustAgent2 = ComponentName.unflattenFromString(
+ "com.user/.AnotherUserTrustAgent");
+ addTrustAgent(systemTrustAgent1, /* isSystemApp= */ true);
+ addTrustAgent(systemTrustAgent2, /* isSystemApp= */ true);
+ addTrustAgent(userTrustAgent1, /* isSystemApp= */ false);
+ addTrustAgent(userTrustAgent2, /* isSystemApp= */ false);
+
+ bootService();
+
+ assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
+ systemTrustAgent1, systemTrustAgent2);
+ assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
+ systemTrustAgent1, systemTrustAgent2, userTrustAgent1, userTrustAgent2);
+ }
+
+ @Test
+ public void firstBootCompleted_defaultTrustAgentEnabled() {
+ ComponentName systemTrustAgent = ComponentName.unflattenFromString(
+ "com.android/.SystemTrustAgent");
+ ComponentName defaultTrustAgent = ComponentName.unflattenFromString(
+ "com.user/.DefaultTrustAgent");
+ addTrustAgent(systemTrustAgent, /* isSystemApp= */ true);
+ addTrustAgent(defaultTrustAgent, /* isSystemApp= */ false);
+ mMockContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.string.config_defaultTrustAgent,
+ defaultTrustAgent.flattenToString());
+
+ bootService();
+
+ assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
+ defaultTrustAgent);
+ assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
+ systemTrustAgent, defaultTrustAgent);
+ }
+
+ @Test
+ public void serviceBooted_knownAgentsNotSet_enabledAgentsNotUpdated() {
+ ComponentName trustAgent1 = ComponentName.unflattenFromString(
+ "com.android/.SystemTrustAgent");
+ ComponentName trustAgent2 = ComponentName.unflattenFromString(
+ "com.android/.AnotherSystemTrustAgent");
+ initializeEnabledAgents(trustAgent1);
+ addTrustAgent(trustAgent1, /* isSystemApp= */ true);
+ addTrustAgent(trustAgent2, /* isSystemApp= */ true);
+
+ bootService();
+
+ assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
+ trustAgent1);
+ assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
+ trustAgent1, trustAgent2);
+ }
+
+ @Test
+ public void serviceBooted_knownAgentsSet_enabledAgentsUpdated() {
+ ComponentName trustAgent1 = ComponentName.unflattenFromString(
+ "com.android/.SystemTrustAgent");
+ ComponentName trustAgent2 = ComponentName.unflattenFromString(
+ "com.android/.AnotherSystemTrustAgent");
+ initializeEnabledAgents(trustAgent1);
+ initializeKnownAgents(trustAgent1);
+ addTrustAgent(trustAgent1, /* isSystemApp= */ true);
+ addTrustAgent(trustAgent2, /* isSystemApp= */ true);
+
+ bootService();
+
+ assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
+ trustAgent1, trustAgent2);
+ assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
+ trustAgent1, trustAgent2);
+ }
+
+ @Test
+ public void newSystemTrustAgent_setToEnabledAndKnown() {
+ bootService();
+ ComponentName newAgentComponentName = ComponentName.unflattenFromString(
+ "com.android/.SystemTrustAgent");
+ addTrustAgent(newAgentComponentName, /* isSystemApp= */ true);
+
+ mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
+
+ assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
+ newAgentComponentName);
+ assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
+ newAgentComponentName);
+ }
+
+ @Test
+ public void newSystemTrustAgent_notEnabledWhenDefaultAgentIsSet() {
+ ComponentName defaultTrustAgent = ComponentName.unflattenFromString(
+ "com.user/.DefaultTrustAgent");
+ addTrustAgent(defaultTrustAgent, /* isSystemApp= */ false);
+ mMockContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.string.config_defaultTrustAgent,
+ defaultTrustAgent.flattenToString());
+ bootService();
+ ComponentName newAgentComponentName = ComponentName.unflattenFromString(
+ "com.android/.SystemTrustAgent");
+ addTrustAgent(newAgentComponentName, /* isSystemApp= */ true);
+
+ mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
+
+ assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
+ defaultTrustAgent);
+ assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
+ defaultTrustAgent, newAgentComponentName);
+ }
+
+ @Test
+ public void newNonSystemTrustAgent_notEnabledButMarkedAsKnown() {
+ bootService();
+ ComponentName newAgentComponentName = ComponentName.unflattenFromString(
+ "com.user/.UserTrustAgent");
+ addTrustAgent(newAgentComponentName, /* isSystemApp= */ false);
+
+ mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
+
+ assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).isEmpty();
+ assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
+ newAgentComponentName);
+ }
+
+ @Test
+ public void existingTrustAgentChanged_notEnabled() {
+ ComponentName systemTrustAgent1 = ComponentName.unflattenFromString(
+ "com.android/.SystemTrustAgent");
+ ComponentName systemTrustAgent2 = ComponentName.unflattenFromString(
+ "com.android/.AnotherSystemTrustAgent");
+ addTrustAgent(systemTrustAgent1, /* isSystemApp= */ true);
+ addTrustAgent(systemTrustAgent2, /* isSystemApp= */ true);
+ bootService();
+ // Simulate user turning off systemTrustAgent2
+ mLockPatternUtils.setEnabledTrustAgents(Collections.singletonList(systemTrustAgent1),
+ TEST_USER_ID);
+
+ mMockContext.sendPackageChangedBroadcast(systemTrustAgent2);
+
+ assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
+ systemTrustAgent1);
+ }
+
+ private void addTrustAgent(ComponentName agentComponentName, boolean isSystemApp) {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ if (isSystemApp) {
+ applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+ }
+
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = agentComponentName.getPackageName();
+ serviceInfo.name = agentComponentName.getClassName();
+ serviceInfo.applicationInfo = applicationInfo;
+
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = serviceInfo;
+ mTrustAgentResolveInfoList.add(resolveInfo);
+ }
+
+ private void initializeEnabledAgents(ComponentName... enabledAgents) {
+ mLockPatternUtils.setEnabledTrustAgents(Lists.newArrayList(enabledAgents), TEST_USER_ID);
+ Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
+ Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
+ }
+
+ private void initializeKnownAgents(ComponentName... knownAgents) {
+ mLockPatternUtils.setKnownTrustAgents(Lists.newArrayList(knownAgents), TEST_USER_ID);
+ Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
+ Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
+ }
+
+ private void bootService() {
+ mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+ mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ }
+
+ private void resetTrustAgentLockSettings() {
+ mLockPatternUtils.setEnabledTrustAgents(Collections.emptyList(), TEST_USER_ID);
+ mLockPatternUtils.setKnownTrustAgents(Collections.emptyList(), TEST_USER_ID);
+ }
+
+ /** A mock Context that allows the test process to send protected broadcasts. */
+ private static final class MockContext extends TestableContext {
+
+ private final ArrayList<BroadcastReceiver> mPackageChangedBroadcastReceivers =
+ new ArrayList<>();
+
+ MockContext(Context base) {
+ super(base);
+ }
+
+ @Override
+ @Nullable
+ public Intent registerReceiverAsUser(BroadcastReceiver receiver,
+ UserHandle user, IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler) {
+
+ if (filter.hasAction(Intent.ACTION_PACKAGE_CHANGED)) {
+ mPackageChangedBroadcastReceivers.add(receiver);
+ }
+ return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
+ scheduler);
+ }
+
+ void sendPackageChangedBroadcast(ComponentName changedComponent) {
+ Intent intent = new Intent(
+ Intent.ACTION_PACKAGE_CHANGED,
+ Uri.fromParts(URI_SCHEME_PACKAGE,
+ changedComponent.getPackageName(), /* fragment= */ null))
+ .putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
+ new String[]{changedComponent.getClassName()})
+ .putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID)
+ .putExtra(Intent.EXTRA_UID, UserHandle.of(TEST_USER_ID).getUid(1234));
+ for (BroadcastReceiver receiver : mPackageChangedBroadcastReceivers) {
+ receiver.onReceive(this, intent);
+ }
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 3f4148b..a45144e 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -588,6 +588,157 @@
});
}
+ @Test
+ public void requestBaseStateOverride() throws RemoteException {
+ TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ mService.getBinderService().registerCallback(callback);
+ flushHandler();
+
+ final IBinder token = new Binder();
+ assertEquals(callback.getLastNotifiedStatus(token),
+ TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+
+ mService.getBinderService().requestBaseStateOverride(token,
+ OTHER_DEVICE_STATE.getIdentifier(),
+ 0 /* flags */);
+ // Flush the handler twice. The first flush ensures the request is added and the policy is
+ // notified, while the second flush ensures the callback is notified once the change is
+ // committed.
+ flushHandler(2 /* count */);
+
+ assertEquals(callback.getLastNotifiedStatus(token),
+ TestDeviceStateManagerCallback.STATUS_ACTIVE);
+ // Committed state changes as there is a requested override.
+ assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
+ assertEquals(mSysPropSetter.getValue(),
+ OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
+ assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
+ assertEquals(mService.getOverrideBaseState().get(), OTHER_DEVICE_STATE);
+ assertFalse(mService.getOverrideState().isPresent());
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+ OTHER_DEVICE_STATE.getIdentifier());
+
+ assertNotNull(callback.getLastNotifiedInfo());
+ assertEquals(callback.getLastNotifiedInfo().baseState,
+ OTHER_DEVICE_STATE.getIdentifier());
+ assertEquals(callback.getLastNotifiedInfo().currentState,
+ OTHER_DEVICE_STATE.getIdentifier());
+
+ mService.getBinderService().cancelBaseStateOverride();
+ flushHandler();
+
+ assertEquals(callback.getLastNotifiedStatus(token),
+ TestDeviceStateManagerCallback.STATUS_CANCELED);
+ // Committed state is set back to the requested state once the override is cleared.
+ assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertEquals(mSysPropSetter.getValue(),
+ DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
+ assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertFalse(mService.getOverrideBaseState().isPresent());
+ assertFalse(mService.getOverrideState().isPresent());
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+ DEFAULT_DEVICE_STATE.getIdentifier());
+
+ assertEquals(callback.getLastNotifiedInfo().baseState,
+ DEFAULT_DEVICE_STATE.getIdentifier());
+ assertEquals(callback.getLastNotifiedInfo().currentState,
+ DEFAULT_DEVICE_STATE.getIdentifier());
+ }
+
+ @Test
+ public void requestBaseStateOverride_cancelledByBaseStateUpdate() throws RemoteException {
+ final DeviceState testDeviceState = new DeviceState(2, "TEST", 0);
+ TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ mService.getBinderService().registerCallback(callback);
+ mProvider.notifySupportedDeviceStates(
+ new DeviceState[]{DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE, testDeviceState });
+ flushHandler();
+
+ final IBinder token = new Binder();
+ assertEquals(callback.getLastNotifiedStatus(token),
+ TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+
+ mService.getBinderService().requestBaseStateOverride(token,
+ OTHER_DEVICE_STATE.getIdentifier(),
+ 0 /* flags */);
+ // Flush the handler twice. The first flush ensures the request is added and the policy is
+ // notified, while the second flush ensures the callback is notified once the change is
+ // committed.
+ flushHandler(2 /* count */);
+
+ assertEquals(callback.getLastNotifiedStatus(token),
+ TestDeviceStateManagerCallback.STATUS_ACTIVE);
+ // Committed state changes as there is a requested override.
+ assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
+ assertEquals(mSysPropSetter.getValue(),
+ OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
+ assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
+ assertEquals(mService.getOverrideBaseState().get(), OTHER_DEVICE_STATE);
+ assertFalse(mService.getOverrideState().isPresent());
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+ OTHER_DEVICE_STATE.getIdentifier());
+
+ assertNotNull(callback.getLastNotifiedInfo());
+ assertEquals(callback.getLastNotifiedInfo().baseState,
+ OTHER_DEVICE_STATE.getIdentifier());
+ assertEquals(callback.getLastNotifiedInfo().currentState,
+ OTHER_DEVICE_STATE.getIdentifier());
+
+ mProvider.setState(testDeviceState.getIdentifier());
+ flushHandler();
+
+ assertEquals(callback.getLastNotifiedStatus(token),
+ TestDeviceStateManagerCallback.STATUS_CANCELED);
+ // Committed state is set to the new base state once the override is cleared.
+ assertEquals(mService.getCommittedState(), Optional.of(testDeviceState));
+ assertEquals(mSysPropSetter.getValue(),
+ testDeviceState.getIdentifier() + ":" + testDeviceState.getName());
+ assertEquals(mService.getBaseState(), Optional.of(testDeviceState));
+ assertFalse(mService.getOverrideBaseState().isPresent());
+ assertFalse(mService.getOverrideState().isPresent());
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+ testDeviceState.getIdentifier());
+
+ assertEquals(callback.getLastNotifiedInfo().baseState,
+ testDeviceState.getIdentifier());
+ assertEquals(callback.getLastNotifiedInfo().currentState,
+ testDeviceState.getIdentifier());
+ }
+
+ @Test
+ public void requestBaseState_unsupportedState() throws RemoteException {
+ TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ mService.getBinderService().registerCallback(callback);
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ final IBinder token = new Binder();
+ mService.getBinderService().requestBaseStateOverride(token,
+ UNSUPPORTED_DEVICE_STATE.getIdentifier(), 0 /* flags */);
+ });
+ }
+
+ @Test
+ public void requestBaseState_invalidState() throws RemoteException {
+ TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ mService.getBinderService().registerCallback(callback);
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ final IBinder token = new Binder();
+ mService.getBinderService().requestBaseStateOverride(token, INVALID_DEVICE_STATE,
+ 0 /* flags */);
+ });
+ }
+
+ @Test
+ public void requestBaseState_beforeRegisteringCallback() {
+ assertThrows(IllegalStateException.class, () -> {
+ final IBinder token = new Binder();
+ mService.getBinderService().requestBaseStateOverride(token,
+ DEFAULT_DEVICE_STATE.getIdentifier(),
+ 0 /* flags */);
+ });
+ }
+
private static void assertArrayEquals(int[] expected, int[] actual) {
Assert.assertTrue(Arrays.equals(expected, actual));
}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index 2297c91..430504c 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -16,6 +16,8 @@
package com.android.server.devicestate;
+import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE;
+import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE;
import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
@@ -57,7 +59,7 @@
@Test
public void addRequest() {
OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
+ 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
assertNull(mStatusListener.getLastStatus(request));
mController.addRequest(request);
@@ -67,14 +69,14 @@
@Test
public void addRequest_cancelExistingRequestThroughNewRequest() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
+ 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
assertNull(mStatusListener.getLastStatus(firstRequest));
mController.addRequest(firstRequest);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 1 /* requestedState */, 0 /* flags */);
+ 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
assertNull(mStatusListener.getLastStatus(secondRequest));
mController.addRequest(secondRequest);
@@ -85,7 +87,7 @@
@Test
public void addRequest_cancelActiveRequest() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
+ 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
mController.addRequest(firstRequest);
@@ -97,30 +99,90 @@
}
@Test
- public void handleBaseStateChanged() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 0 /* requestedState */,
- DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */);
+ public void addBaseStateRequest() {
+ OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+ assertNull(mStatusListener.getLastStatus(request));
- mController.addRequest(firstRequest);
+ mController.addBaseStateRequest(request);
+ assertEquals(mStatusListener.getLastStatus(request).intValue(), STATUS_ACTIVE);
+ }
+
+ @Test
+ public void addBaseStateRequest_cancelExistingBaseStateRequestThroughNewRequest() {
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+ assertNull(mStatusListener.getLastStatus(firstRequest));
+
+ mController.addBaseStateRequest(firstRequest);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+
+ OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+ assertNull(mStatusListener.getLastStatus(secondRequest));
+
+ mController.addBaseStateRequest(secondRequest);
+ assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
+ }
+
+ @Test
+ public void addBaseStateRequest_cancelActiveBaseStateRequest() {
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+
+ mController.addBaseStateRequest(firstRequest);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
- mController.handleBaseStateChanged();
+ mController.cancelBaseStateOverrideRequest();
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
@Test
+ public void handleBaseStateChanged() {
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* requestedState */,
+ DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */,
+ OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+
+ OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* requestedState */,
+ 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+
+ mController.addRequest(firstRequest);
+
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+
+ mController.addBaseStateRequest(baseStateRequest);
+
+ assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
+
+ mController.handleBaseStateChanged(1);
+
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
+ assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED);
+ }
+
+ @Test
public void handleProcessDied() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
+ 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+
+ OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 1 /* requestedState */,
+ 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
mController.addRequest(firstRequest);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+ mController.addBaseStateRequest(baseStateRequest);
+ assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
+
mController.handleProcessDied(0);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
+ assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED);
}
@Test
@@ -128,13 +190,20 @@
mController.setStickyRequestsAllowed(true);
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
+ 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+
+ OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
mController.addRequest(firstRequest);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+ mController.addBaseStateRequest(baseStateRequest);
+ assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
+
mController.handleProcessDied(0);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+ assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED);
mController.cancelStickyRequest();
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
@@ -143,22 +212,31 @@
@Test
public void handleNewSupportedStates() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 1 /* requestedState */, 0 /* flags */);
+ 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+
+ OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 1 /* requestedState */,
+ 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
mController.addRequest(firstRequest);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
- mController.handleNewSupportedStates(new int[]{ 0, 1 });
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+ mController.addBaseStateRequest(baseStateRequest);
+ assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
- mController.handleNewSupportedStates(new int[]{ 0 });
+ mController.handleNewSupportedStates(new int[]{0, 1});
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+ assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
+
+ mController.handleNewSupportedStates(new int[]{0});
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
+ assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED);
}
@Test
public void cancelOverrideRequestsTest() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 1 /* requestedState */, 0 /* flags */);
+ 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
mController.addRequest(firstRequest);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
diff --git a/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
index 4ef156e..e0bef1a 100644
--- a/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
@@ -18,21 +18,26 @@
import static com.google.common.truth.Truth.assertWithMessage;
-import androidx.test.InstrumentationRegistry;
+import static org.mockito.Mockito.mock;
-import java.lang.System;
-import java.util.Arrays;
+import android.hardware.display.DisplayManagerInternal;
+
+import androidx.test.InstrumentationRegistry;
import org.junit.Before;
import org.junit.Test;
+import java.util.Arrays;
+
public class DisplayWhiteBalanceTintControllerTest {
private DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
@Before
public void setUp() {
- mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController();
+ DisplayManagerInternal displayManagerInternal = mock(DisplayManagerInternal.class);
+ mDisplayWhiteBalanceTintController =
+ new DisplayWhiteBalanceTintController(displayManagerInternal);
mDisplayWhiteBalanceTintController.setUp(InstrumentationRegistry.getContext(), true);
mDisplayWhiteBalanceTintController.setActivated(true);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index e04edc6..96707fd 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -216,6 +216,25 @@
assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
}
+ @Test
+ public void assertIsUserSwitcherEnabled() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+ setMaxSupportedUsers(8);
+ assertThat(mUserManagerService.isUserSwitcherEnabled(true, userId)).isTrue();
+
+ setUserSwitch(false);
+ assertThat(mUserManagerService.isUserSwitcherEnabled(true, userId)).isFalse();
+
+ setUserSwitch(true);
+ assertThat(mUserManagerService.isUserSwitcherEnabled(false, userId)).isTrue();
+
+ mUserManagerService.setUserRestriction(UserManager.DISALLOW_ADD_USER, true, userId);
+ assertThat(mUserManagerService.isUserSwitcherEnabled(false, userId)).isFalse();
+
+ mUserManagerService.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, userId);
+ setMaxSupportedUsers(1);
+ assertThat(mUserManagerService.isUserSwitcherEnabled(true, userId)).isFalse();
+ }
@Test
public void assertIsUserSwitcherEnabledOnShowMultiuserUI() throws Exception {
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 107bbe1..2918365 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -79,10 +79,7 @@
<activity android:name="com.android.server.wm.ActivityOptionsTest$MainActivity"
android:turnScreenOn="true"
android:showWhenLocked="true" />
- <activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity"
- android:theme="@style/WhiteBackgroundTheme"
- android:turnScreenOn="true"
- android:showWhenLocked="true"/>
+ <activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity" />
<activity android:name="android.view.cts.surfacevalidator.CapturedActivity"/>
<service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
index 8cd8e9b..0b58428 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
@@ -16,10 +16,6 @@
package com.android.server.wm;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowInsets.Type.displayCutout;
-import static android.view.WindowInsets.Type.statusBars;
-
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertNotNull;
@@ -27,31 +23,20 @@
import android.app.Activity;
import android.app.Instrumentation;
-import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.ColorSpace;
import android.graphics.GraphicBuffer;
-import android.graphics.Insets;
import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.DataSpace;
-import android.hardware.HardwareBuffer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.platform.test.annotations.Presubmit;
-import android.view.IWindowManager;
import android.view.PointerIcon;
import android.view.SurfaceControl;
-import android.view.cts.surfacevalidator.BitmapPixelChecker;
-import android.view.cts.surfacevalidator.PixelColor;
-import android.view.cts.surfacevalidator.SaveBitmapHelper;
+import android.view.WindowManager;
import android.window.ScreenCapture;
-import android.window.ScreenCapture.SyncScreenCaptureListener;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
@@ -60,7 +45,6 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TestName;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -76,8 +60,6 @@
private static final int BUFFER_HEIGHT = 100;
private final Instrumentation mInstrumentation = getInstrumentation();
- @Rule
- public TestName mTestName = new TestName();
@Rule
public ActivityTestRule<ScreenshotActivity> mActivityRule =
@@ -113,8 +95,8 @@
buffer.unlockCanvasAndPost(canvas);
t.show(secureSC)
- .setBuffer(secureSC, HardwareBuffer.createFromGraphicBuffer(buffer))
- .setDataSpace(secureSC, DataSpace.DATASPACE_SRGB)
+ .setBuffer(secureSC, buffer)
+ .setColorSpace(secureSC, ColorSpace.get(ColorSpace.Named.SRGB))
.apply(true);
ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(secureSC)
@@ -130,69 +112,15 @@
Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
screenshot.recycle();
- BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(PixelColor.RED);
- Rect bounds = new Rect(0, 0, swBitmap.getWidth(), swBitmap.getHeight());
- int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds);
- int sizeOfBitmap = bounds.width() * bounds.height();
+ int numMatchingPixels = PixelChecker.getNumMatchingPixels(swBitmap,
+ new PixelColor(PixelColor.RED));
+ long sizeOfBitmap = swBitmap.getWidth() * swBitmap.getHeight();
boolean success = numMatchingPixels == sizeOfBitmap;
swBitmap.recycle();
assertTrue(success);
}
- @Test
- public void testCaptureDisplay() throws RemoteException {
- IWindowManager windowManager = IWindowManager.Stub.asInterface(
- ServiceManager.getService(Context.WINDOW_SERVICE));
- SurfaceControl sc = new SurfaceControl.Builder()
- .setName("Layer")
- .setCallsite("testCaptureDisplay")
- .build();
-
- SurfaceControl.Transaction t = mActivity.addChildSc(sc);
- mInstrumentation.waitForIdleSync();
-
- GraphicBuffer buffer = GraphicBuffer.create(BUFFER_WIDTH, BUFFER_HEIGHT,
- PixelFormat.RGBA_8888,
- GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
- | GraphicBuffer.USAGE_SW_WRITE_RARELY);
-
- Canvas canvas = buffer.lockCanvas();
- canvas.drawColor(Color.RED);
- buffer.unlockCanvasAndPost(canvas);
-
- Point point = mActivity.getPositionBelowStatusBar();
- t.show(sc)
- .setBuffer(sc, HardwareBuffer.createFromGraphicBuffer(buffer))
- .setDataSpace(sc, DataSpace.DATASPACE_SRGB)
- .setPosition(sc, point.x, point.y)
- .apply(true);
-
- SyncScreenCaptureListener listener = new SyncScreenCaptureListener();
- windowManager.captureDisplay(DEFAULT_DISPLAY, null, listener.getScreenCaptureListener());
- ScreenCapture.ScreenshotHardwareBuffer hardwareBuffer = listener.waitForScreenshot();
- assertNotNull(hardwareBuffer);
-
- Bitmap screenshot = hardwareBuffer.asBitmap();
- assertNotNull(screenshot);
-
- Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
- screenshot.recycle();
-
- BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(PixelColor.RED);
- Rect bounds = new Rect(point.x, point.y, BUFFER_WIDTH + point.x, BUFFER_HEIGHT + point.y);
- int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds);
- int pixelMatchSize = bounds.width() * bounds.height();
- boolean success = numMatchingPixels == pixelMatchSize;
-
- if (!success) {
- SaveBitmapHelper.saveBitmap(swBitmap, getClass(), mTestName, "failedImage");
- }
- swBitmap.recycle();
- assertTrue("numMatchingPixels=" + numMatchingPixels + " pixelMatchSize=" + pixelMatchSize,
- success);
- }
-
public static class ScreenshotActivity extends Activity {
private static final long WAIT_TIMEOUT_S = 5;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -202,6 +130,7 @@
super.onCreate(savedInstanceState);
getWindow().getDecorView().setPointerIcon(
PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL));
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
SurfaceControl.Transaction addChildSc(SurfaceControl surfaceControl) {
@@ -219,14 +148,88 @@
}
return t;
}
+ }
- public Point getPositionBelowStatusBar() {
- Insets statusBarInsets = getWindow()
- .getDecorView()
- .getRootWindowInsets()
- .getInsets(statusBars() | displayCutout());
+ public abstract static class PixelChecker {
+ static int getNumMatchingPixels(Bitmap bitmap, PixelColor pixelColor) {
+ int numMatchingPixels = 0;
+ for (int x = 0; x < bitmap.getWidth(); x++) {
+ for (int y = 0; y < bitmap.getHeight(); y++) {
+ int color = bitmap.getPixel(x, y);
+ if (matchesColor(pixelColor, color)) {
+ numMatchingPixels++;
+ }
+ }
+ }
+ return numMatchingPixels;
+ }
- return new Point(statusBarInsets.left, statusBarInsets.top);
+ static boolean matchesColor(PixelColor expectedColor, int color) {
+ final float red = Color.red(color);
+ final float green = Color.green(color);
+ final float blue = Color.blue(color);
+ final float alpha = Color.alpha(color);
+
+ return alpha <= expectedColor.mMaxAlpha
+ && alpha >= expectedColor.mMinAlpha
+ && red <= expectedColor.mMaxRed
+ && red >= expectedColor.mMinRed
+ && green <= expectedColor.mMaxGreen
+ && green >= expectedColor.mMinGreen
+ && blue <= expectedColor.mMaxBlue
+ && blue >= expectedColor.mMinBlue;
+ }
+ }
+
+ public static class PixelColor {
+ public static final int BLACK = 0xFF000000;
+ public static final int RED = 0xFF0000FF;
+ public static final int GREEN = 0xFF00FF00;
+ public static final int BLUE = 0xFFFF0000;
+ public static final int YELLOW = 0xFF00FFFF;
+ public static final int MAGENTA = 0xFFFF00FF;
+ public static final int WHITE = 0xFFFFFFFF;
+
+ public static final int TRANSPARENT_RED = 0x7F0000FF;
+ public static final int TRANSPARENT_BLUE = 0x7FFF0000;
+ public static final int TRANSPARENT = 0x00000000;
+
+ // Default to black
+ public short mMinAlpha;
+ public short mMaxAlpha;
+ public short mMinRed;
+ public short mMaxRed;
+ public short mMinBlue;
+ public short mMaxBlue;
+ public short mMinGreen;
+ public short mMaxGreen;
+
+ public PixelColor(int color) {
+ short alpha = (short) ((color >> 24) & 0xFF);
+ short blue = (short) ((color >> 16) & 0xFF);
+ short green = (short) ((color >> 8) & 0xFF);
+ short red = (short) (color & 0xFF);
+
+ mMinAlpha = (short) getMinValue(alpha);
+ mMaxAlpha = (short) getMaxValue(alpha);
+ mMinRed = (short) getMinValue(red);
+ mMaxRed = (short) getMaxValue(red);
+ mMinBlue = (short) getMinValue(blue);
+ mMaxBlue = (short) getMaxValue(blue);
+ mMinGreen = (short) getMinValue(green);
+ mMaxGreen = (short) getMaxValue(green);
+ }
+
+ public PixelColor() {
+ this(BLACK);
+ }
+
+ private int getMinValue(short color) {
+ return Math.max(color - 4, 0);
+ }
+
+ private int getMaxValue(short color) {
+ return Math.min(color + 4, 0xFF);
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index f5fc5c1..70e6f29 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -39,6 +39,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
import android.app.ActivityManagerInternal;
@@ -81,6 +82,7 @@
import com.android.server.display.color.ColorDisplayService;
import com.android.server.firewall.IntentFirewall;
import com.android.server.input.InputManagerService;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerService;
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.policy.WindowManagerPolicy;
@@ -93,6 +95,7 @@
import org.mockito.MockSettings;
import org.mockito.Mockito;
import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -283,6 +286,16 @@
// StatusBarManagerInternal
final StatusBarManagerInternal sbmi = mock(StatusBarManagerInternal.class);
doReturn(sbmi).when(() -> LocalServices.getService(eq(StatusBarManagerInternal.class)));
+
+ // UserManagerInternal
+ final UserManagerInternal umi = mock(UserManagerInternal.class);
+ doReturn(umi).when(() -> LocalServices.getService(UserManagerInternal.class));
+ Answer<Boolean> isUserVisibleAnswer = invocation -> {
+ int userId = invocation.getArgument(0);
+ return userId == mWmService.mCurrentUserId;
+ };
+ when(umi.isUserVisible(anyInt())).thenAnswer(isUserVisibleAnswer);
+ when(umi.isUserVisible(anyInt(), anyInt())).thenAnswer(isUserVisibleAnswer);
}
private void setUpActivityTaskManagerService() {
@@ -403,6 +416,7 @@
LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
}
Description getDescription() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 9bdf750..61cf8cc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
import static android.window.TaskFragmentOrganizer.getTransitionType;
@@ -76,6 +77,7 @@
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentOrganizerToken;
+import android.window.TaskFragmentParentInfo;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -271,7 +273,7 @@
@Test
public void testOnTaskFragmentParentInfoChanged() {
setupMockParent(mTaskFragment, mTask);
- mTask.getConfiguration().smallestScreenWidthDp = 10;
+ mTask.getTaskFragmentParentInfo().getConfiguration().smallestScreenWidthDp = 10;
mController.onTaskFragmentAppeared(
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
@@ -295,7 +297,7 @@
// Trigger callback if the size is changed.
clearInvocations(mOrganizer);
- mTask.getConfiguration().smallestScreenWidthDp = 100;
+ mTask.getTaskFragmentParentInfo().getConfiguration().smallestScreenWidthDp = 100;
mController.onTaskFragmentInfoChanged(
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
@@ -304,7 +306,8 @@
// Trigger callback if the windowing mode is changed.
clearInvocations(mOrganizer);
- mTask.getConfiguration().windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED);
+ mTask.getTaskFragmentParentInfo().getConfiguration().windowConfiguration
+ .setWindowingMode(WINDOWING_MODE_PINNED);
mController.onTaskFragmentInfoChanged(
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
@@ -1268,7 +1271,7 @@
final TaskFragmentTransaction.Change change = changes.get(0);
assertEquals(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED, change.getType());
assertEquals(task.mTaskId, change.getTaskId());
- assertEquals(task.getConfiguration(), change.getTaskConfiguration());
+ assertEquals(task.getTaskFragmentParentInfo(), change.getTaskFragmentParentInfo());
}
/** Asserts that there will be a transaction for TaskFragment error. */
@@ -1316,8 +1319,8 @@
/** Setups the mock Task as the parent of the given TaskFragment. */
private static void setupMockParent(TaskFragment taskFragment, Task mockParent) {
doReturn(mockParent).when(taskFragment).getTask();
- final Configuration taskConfig = new Configuration();
- doReturn(taskConfig).when(mockParent).getConfiguration();
+ doReturn(new TaskFragmentParentInfo(new Configuration(), DEFAULT_DISPLAY, true))
+ .when(mockParent).getTaskFragmentParentInfo();
// Task needs to be visible
mockParent.lastActiveTime = 100;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index e2dff96..3331839 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -34,6 +34,7 @@
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
@@ -1077,6 +1078,39 @@
}
@Test
+ public void testIsBehindStartingWindowChange() {
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
+ final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ final ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity0 = createActivityRecord(task);
+ final ActivityRecord activity1 = createActivityRecord(task);
+ doReturn(true).when(activity1).hasStartingWindow();
+
+ // Start states.
+ changes.put(activity0, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ changes.put(activity1, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+ // End states.
+ activity0.mVisibleRequested = false;
+ activity1.mVisibleRequested = true;
+
+ participants.add(activity0);
+ participants.add(activity1);
+ final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ participants, changes);
+ final TransitionInfo info = Transition.calculateTransitionInfo(
+ transition.mType, 0 /* flags */, targets, changes, mMockT);
+
+ // All windows in the Task should have FLAG_IS_BEHIND_STARTING_WINDOW because the starting
+ // window should cover the whole Task.
+ assertEquals(2, info.getChanges().size());
+ assertTrue(info.getChanges().get(0).hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW));
+ assertTrue(info.getChanges().get(1).hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW));
+
+ }
+
+ @Test
public void testFlagInTaskWithEmbeddedActivity() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
@@ -1123,7 +1157,7 @@
}
@Test
- public void testFlagFillsTask() {
+ public void testFlagFillsTask_embeddingNotFillingTask() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
final ArraySet<WindowContainer> participants = transition.mParticipants;
@@ -1168,6 +1202,67 @@
}
@Test
+ public void testFlagFillsTask_openActivityFillingTask() {
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
+ final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ final ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ final Task task = createTask(mDisplayContent);
+ // Set to multi-windowing mode in order to set bounds.
+ task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ final Rect taskBounds = new Rect(0, 0, 500, 1000);
+ task.setBounds(taskBounds);
+ final ActivityRecord activity = createActivityRecord(task);
+ // Start states: set bounds to make sure the start bounds is ignored if it is not visible.
+ activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
+ activity.mVisibleRequested = false;
+ changes.put(activity, new Transition.ChangeInfo(activity));
+ // End states: reset bounds to fill Task.
+ activity.getConfiguration().windowConfiguration.setBounds(taskBounds);
+ activity.mVisibleRequested = true;
+
+ participants.add(activity);
+ final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ participants, changes);
+ final TransitionInfo info = Transition.calculateTransitionInfo(
+ transition.mType, 0 /* flags */, targets, changes, mMockT);
+
+ // Opening activity that is filling Task after transition should have the flag.
+ assertEquals(1, info.getChanges().size());
+ assertTrue(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK));
+ }
+
+ @Test
+ public void testFlagFillsTask_closeActivityFillingTask() {
+ final Transition transition = createTestTransition(TRANSIT_CLOSE);
+ final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ final ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ final Task task = createTask(mDisplayContent);
+ // Set to multi-windowing mode in order to set bounds.
+ task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ final Rect taskBounds = new Rect(0, 0, 500, 1000);
+ task.setBounds(taskBounds);
+ final ActivityRecord activity = createActivityRecord(task);
+ // Start states: fills Task without override.
+ activity.mVisibleRequested = true;
+ changes.put(activity, new Transition.ChangeInfo(activity));
+ // End states: set bounds to make sure the start bounds is ignored if it is not visible.
+ activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
+ activity.mVisibleRequested = false;
+
+ participants.add(activity);
+ final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ participants, changes);
+ final TransitionInfo info = Transition.calculateTransitionInfo(
+ transition.mType, 0 /* flags */, targets, changes, mMockT);
+
+ // Closing activity that is filling Task before transition should have the flag.
+ assertEquals(1, info.getChanges().size());
+ assertTrue(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK));
+ }
+
+ @Test
public void testIncludeEmbeddedActivityReparent() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
final Task task = createTask(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 8b63904..46b4b76 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -41,7 +41,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
@@ -69,7 +68,6 @@
import android.view.View;
import android.view.WindowManager;
import android.window.ClientWindowFrames;
-import android.window.ScreenCapture;
import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
@@ -425,45 +423,6 @@
LETTERBOX_BACKGROUND_SOLID_COLOR)).isFalse();
}
- @Test
- public void testCaptureDisplay() {
- Rect displayBounds = new Rect(0, 0, 100, 200);
- spyOn(mDisplayContent);
- when(mDisplayContent.getBounds()).thenReturn(displayBounds);
-
- // Null captureArgs
- ScreenCapture.LayerCaptureArgs resultingArgs =
- mWm.getCaptureArgs(DEFAULT_DISPLAY, null /* captureArgs */);
- assertEquals(displayBounds, resultingArgs.mSourceCrop);
-
- // Non null captureArgs, didn't set rect
- ScreenCapture.CaptureArgs captureArgs = new ScreenCapture.CaptureArgs.Builder<>().build();
- resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
- assertEquals(displayBounds, resultingArgs.mSourceCrop);
-
- // Non null captureArgs, invalid rect
- captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
- .setSourceCrop(new Rect(0, 0, -1, -1))
- .build();
- resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
- assertEquals(displayBounds, resultingArgs.mSourceCrop);
-
- // Non null captureArgs, null rect
- captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
- .setSourceCrop(null)
- .build();
- resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
- assertEquals(displayBounds, resultingArgs.mSourceCrop);
-
- // Non null captureArgs, valid rect
- Rect validRect = new Rect(0, 0, 10, 50);
- captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
- .setSourceCrop(validRect)
- .build();
- resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
- assertEquals(validRect, resultingArgs.mSourceCrop);
- }
-
private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
when(remoteToken.toWindowContainerToken()).thenReturn(wct);
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 9562c1a..e90a376 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -514,6 +514,8 @@
private boolean mConfigured;
private boolean mAudioAccessoryConnected;
private boolean mAudioAccessorySupported;
+ private boolean mConnectedToDataDisabledPort;
+ private int mPowerBrickConnectionStatus;
private UsbAccessory mCurrentAccessory;
private int mUsbNotificationId;
@@ -952,12 +954,19 @@
&& status.isRoleCombinationSupported(POWER_ROLE_SOURCE,
DATA_ROLE_DEVICE)
&& status.isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_DEVICE);
+
+ boolean usbDataDisabled =
+ status.getUsbDataStatus() != UsbPortStatus.DATA_STATUS_ENABLED;
+ mConnectedToDataDisabledPort = status.isConnected() && usbDataDisabled;
+ mPowerBrickConnectionStatus = status.getPowerBrickConnectionStatus();
} else {
mHostConnected = false;
mSourcePower = false;
mSinkPower = false;
mAudioAccessoryConnected = false;
mSupportsAllCombinations = false;
+ mConnectedToDataDisabledPort = false;
+ mPowerBrickConnectionStatus = UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN;
}
if (mHostConnected) {
@@ -1265,6 +1274,12 @@
} else if (mHostConnected && mSinkPower && (mUsbCharging || mUsbAccessoryConnected)) {
titleRes = com.android.internal.R.string.usb_charging_notification_title;
id = SystemMessage.NOTE_USB_CHARGING;
+ } else if (mSinkPower && mConnectedToDataDisabledPort
+ && mPowerBrickConnectionStatus != UsbPortStatus.POWER_BRICK_STATUS_CONNECTED) {
+ // Show charging notification when USB Data is disabled on the port, and not
+ // connected to a wall charger.
+ titleRes = com.android.internal.R.string.usb_charging_notification_title;
+ id = SystemMessage.NOTE_USB_CHARGING;
}
if (id != mUsbNotificationId || force) {
// clear notification if title needs changing
diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java
index 58974ee..061b71b 100644
--- a/telephony/java/android/telephony/AnomalyReporter.java
+++ b/telephony/java/android/telephony/AnomalyReporter.java
@@ -105,17 +105,23 @@
* @param carrierId the carrier of the id associated with this event.
*/
public static void reportAnomaly(@NonNull UUID eventId, String description, int carrierId) {
- // Don't report if the server-side flag isn't loaded, as it implies other anomaly report
- // related config hasn't loaded.
- boolean isAnomalyReportEnabledFromServer = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_TELEPHONY, KEY_IS_TELEPHONY_ANOMALY_REPORT_ENABLED, false);
- if (!isAnomalyReportEnabledFromServer) return;
-
if (sContext == null) {
Rlog.w(TAG, "AnomalyReporter not yet initialized, dropping event=" + eventId);
return;
}
+ // Don't report if the server-side flag isn't loaded, as it implies other anomaly report
+ // related config hasn't loaded.
+ try {
+ boolean isAnomalyReportEnabledFromServer = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_TELEPHONY, KEY_IS_TELEPHONY_ANOMALY_REPORT_ENABLED,
+ false);
+ if (!isAnomalyReportEnabledFromServer) return;
+ } catch (Exception e) {
+ Rlog.w(TAG, "Unable to read device config, dropping event=" + eventId);
+ return;
+ }
+
TelephonyStatsLog.write(
TELEPHONY_ANOMALY_DETECTED,
carrierId,
diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java
index c75de42..ac892da 100644
--- a/telephony/java/android/telephony/NetworkService.java
+++ b/telephony/java/android/telephony/NetworkService.java
@@ -265,7 +265,7 @@
/** @hide */
@Override
public void onDestroy() {
- mHandlerThread.quit();
+ mHandlerThread.quitSafely();
super.onDestroy();
}
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index cb985bf..eb96d37 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -18,6 +18,7 @@
import static android.text.TextUtils.formatSimple;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
@@ -37,6 +38,9 @@
import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.Parcelable;
+import android.telephony.SubscriptionManager.ProfileClass;
+import android.telephony.SubscriptionManager.SimDisplayNameSource;
+import android.telephony.SubscriptionManager.SubscriptionType;
import android.telephony.SubscriptionManager.UsageSetting;
import android.text.TextUtils;
import android.util.DisplayMetrics;
@@ -55,7 +59,6 @@
* A Parcelable class for Subscription Information.
*/
public class SubscriptionInfo implements Parcelable {
-
/**
* Size of text to render on the icon.
*/
@@ -65,162 +68,180 @@
* Subscription Identifier, this is a device unique number
* and not an index into an array
*/
- private int mId;
+ private final int mId;
/**
- * The GID for a SIM that maybe associated with this subscription, empty if unknown
+ * The ICCID of the SIM that is associated with this subscription, empty if unknown.
*/
- private String mIccId;
+ @NonNull
+ private final String mIccId;
/**
- * The index of the slot that currently contains the subscription
- * and not necessarily unique and maybe INVALID_SLOT_ID if unknown
+ * The index of the SIM slot that currently contains the subscription and not necessarily unique
+ * and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or the subscription
+ * is inactive.
*/
- private int mSimSlotIndex;
+ private final int mSimSlotIndex;
/**
- * The name displayed to the user that identifies this subscription
+ * The name displayed to the user that identifies this subscription. This name is used
+ * in Settings page and can be renamed by the user.
*/
- private CharSequence mDisplayName;
+ @NonNull
+ private final CharSequence mDisplayName;
/**
- * String that identifies SPN/PLMN
- * TODO : Add a new field that identifies only SPN for a sim
+ * The name displayed to the user that identifies subscription provider name. This name is the
+ * SPN displayed in status bar and many other places. Can't be renamed by the user.
*/
- private CharSequence mCarrierName;
+ @NonNull
+ private final CharSequence mCarrierName;
/**
* The subscription carrier id.
+ *
* @see TelephonyManager#getSimCarrierId()
*/
- private int mCarrierId;
+ private final int mCarrierId;
/**
- * The source of the name, NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SPN,
- * NAME_SOURCE_SIM_PNN, or NAME_SOURCE_USER_INPUT.
+ * The source of the {@link #mCarrierName}.
*/
- private int mNameSource;
+ @SimDisplayNameSource
+ private final int mNameSource;
/**
- * The color to be used for tinting the icon when displaying to the user
+ * The color to be used for tinting the icon when displaying to the user.
*/
- private int mIconTint;
+ private final int mIconTint;
/**
- * A number presented to the user identify this subscription
+ * The number presented to the user identify this subscription.
*/
- private String mNumber;
+ @NonNull
+ private final String mNumber;
/**
- * Data roaming state, DATA_ROAMING_ENABLE, DATA_ROAMING_DISABLE
+ * Whether user enables data roaming for this subscription or not. Either
+ * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or
+ * {@link SubscriptionManager#DATA_ROAMING_DISABLE}.
*/
- private int mDataRoaming;
+ private final int mDataRoaming;
/**
- * SIM icon bitmap cache
- */
- @Nullable private Bitmap mIconBitmap;
-
- /**
- * Mobile Country Code
- */
- private String mMcc;
-
- /**
- * Mobile Network Code
- */
- private String mMnc;
-
- /**
- * EHPLMNs associated with the subscription
- */
- private String[] mEhplmns;
-
- /**
- * HPLMNs associated with the subscription
- */
- private String[] mHplmns;
-
- /**
- * ISO Country code for the subscription's provider
- */
- private String mCountryIso;
-
- /**
- * Whether the subscription is an embedded one.
- */
- private boolean mIsEmbedded;
-
- /**
- * The access rules for this subscription, if it is embedded and defines any.
- * This does not include access rules for non-embedded subscriptions.
+ * SIM icon bitmap cache.
*/
@Nullable
- private UiccAccessRule[] mNativeAccessRules;
+ private Bitmap mIconBitmap;
+
+ /**
+ * Mobile Country Code.
+ */
+ @Nullable
+ private final String mMcc;
+
+ /**
+ * Mobile Network Code.
+ */
+ @Nullable
+ private final String mMnc;
+
+ /**
+ * EHPLMNs associated with the subscription.
+ */
+ @NonNull
+ private final String[] mEhplmns;
+
+ /**
+ * HPLMNs associated with the subscription.
+ */
+ @NonNull
+ private final String[] mHplmns;
+
+ /**
+ * ISO Country code for the subscription's provider.
+ */
+ @NonNull
+ private final String mCountryIso;
+
+ /**
+ * Whether the subscription is from eSIM.
+ */
+ private final boolean mIsEmbedded;
+
+ /**
+ * The access rules for this subscription, if it is embedded and defines any. This does not
+ * include access rules for non-embedded subscriptions.
+ */
+ @Nullable
+ private final UiccAccessRule[] mNativeAccessRules;
/**
* The carrier certificates for this subscription that are saved in carrier configs.
* This does not include access rules from the Uicc, whether embedded or non-embedded.
*/
@Nullable
- private UiccAccessRule[] mCarrierConfigAccessRules;
+ private final UiccAccessRule[] mCarrierConfigAccessRules;
/**
* The string ID of the SIM card. It is the ICCID of the active profile for a UICC card and the
* EID for an eUICC card.
*/
- private String mCardString;
+ @NonNull
+ private final String mCardString;
/**
- * The card ID of the SIM card. This maps uniquely to the card string.
+ * The card ID of the SIM card. This maps uniquely to {@link #mCardString}.
*/
- private int mCardId;
+ private final int mCardId;
/**
* Whether the subscription is opportunistic.
*/
- private boolean mIsOpportunistic;
+ private final boolean mIsOpportunistic;
/**
- * A UUID assigned to the subscription group. It returns null if not assigned.
- * Check {@link SubscriptionManager#createSubscriptionGroup(List)} for more details.
+ * A UUID assigned to the subscription group. {@code null} if not assigned.
+ *
+ * @see SubscriptionManager#createSubscriptionGroup(List)
*/
@Nullable
- private ParcelUuid mGroupUUID;
+ private final ParcelUuid mGroupUuid;
/**
- * A package name that specifies who created the group. Null if mGroupUUID is null.
+ * A package name that specifies who created the group. Empty if not available.
*/
- private String mGroupOwner;
+ @NonNull
+ private final String mGroupOwner;
/**
- * Whether group of the subscription is disabled.
- * This is only useful if it's a grouped opportunistic subscription. In this case, if all
- * primary (non-opportunistic) subscriptions in the group are deactivated (unplugged pSIM
- * or deactivated eSIM profile), we should disable this opportunistic subscription.
+ * Whether group of the subscription is disabled. This is only useful if it's a grouped
+ * opportunistic subscription. In this case, if all primary (non-opportunistic) subscriptions
+ * in the group are deactivated (unplugged pSIM or deactivated eSIM profile), we should disable
+ * this opportunistic subscription.
*/
- private boolean mIsGroupDisabled = false;
+ private final boolean mIsGroupDisabled;
/**
- * Profile class, PROFILE_CLASS_TESTING, PROFILE_CLASS_OPERATIONAL
- * PROFILE_CLASS_PROVISIONING, or PROFILE_CLASS_UNSET.
- * A profile on the eUICC can be defined as test, operational, provisioning, or unset.
- * The profile class will be populated from the profile metadata if present. Otherwise,
- * the profile class defaults to unset if there is no profile metadata or the subscription
- * is not on an eUICC ({@link #isEmbedded} returns false).
+ * The profile class populated from the profile metadata if present. Otherwise,
+ * the profile class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no
+ * profile metadata or the subscription is not on an eUICC ({@link #isEmbedded} returns
+ * {@code false}).
*/
- private int mProfileClass;
+ @ProfileClass
+ private final int mProfileClass;
/**
- * Type of subscription
+ * Type of the subscription.
*/
- private int mSubscriptionType;
+ @SubscriptionType
+ private final int mType;
/**
* Whether uicc applications are configured to enable or disable.
* By default it's true.
*/
- private boolean mAreUiccApplicationsEnabled = true;
+ private final boolean mAreUiccApplicationsEnabled;
/**
* The port index of the Uicc card.
@@ -230,25 +251,16 @@
/**
* Subscription's preferred usage setting.
*/
- private @UsageSetting int mUsageSetting = SubscriptionManager.USAGE_SETTING_UNKNOWN;
-
- /**
- * Public copy constructor.
- * @hide
- */
- public SubscriptionInfo(SubscriptionInfo info) {
- this(info.mId, info.mIccId, info.mSimSlotIndex, info.mDisplayName, info.mCarrierName,
- info.mNameSource, info.mIconTint, info.mNumber, info.mDataRoaming, info.mIconBitmap,
- info.mMcc, info.mMnc, info.mCountryIso, info.mIsEmbedded, info.mNativeAccessRules,
- info.mCardString, info.mCardId, info.mIsOpportunistic,
- info.mGroupUUID == null ? null : info.mGroupUUID.toString(), info.mIsGroupDisabled,
- info.mCarrierId, info.mProfileClass, info.mSubscriptionType, info.mGroupOwner,
- info.mCarrierConfigAccessRules, info.mAreUiccApplicationsEnabled);
- }
+ @UsageSetting
+ private final int mUsageSetting;
/**
* @hide
+ *
+ * @deprecated Use {@link SubscriptionInfo.Builder}.
*/
+ // TODO: Clean up after external usages moved to builder model.
+ @Deprecated
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@@ -262,7 +274,11 @@
/**
* @hide
+ *
+ * @deprecated Use {@link SubscriptionInfo.Builder}.
*/
+ // TODO: Clean up after external usages moved to builder model.
+ @Deprecated
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@@ -276,7 +292,11 @@
/**
* @hide
+ *
+ * @deprecated Use {@link SubscriptionInfo.Builder}.
*/
+ // TODO: Clean up after external usages moved to builder model.
+ @Deprecated
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@@ -293,7 +313,11 @@
/**
* @hide
+ *
+ * @deprecated Use {@link SubscriptionInfo.Builder}.
*/
+ // TODO: Clean up after external usages moved to builder model.
+ @Deprecated
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@@ -311,49 +335,94 @@
/**
* @hide
+ *
+ * @deprecated Use {@link SubscriptionInfo.Builder}.
*/
+ // TODO: Clean up after external usages moved to builder model.
+ @Deprecated
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@Nullable UiccAccessRule[] nativeAccessRules, String cardString, int cardId,
- boolean isOpportunistic, @Nullable String groupUUID, boolean isGroupDisabled,
+ boolean isOpportunistic, @Nullable String groupUuid, boolean isGroupDisabled,
int carrierId, int profileClass, int subType, @Nullable String groupOwner,
@Nullable UiccAccessRule[] carrierConfigAccessRules,
boolean areUiccApplicationsEnabled, int portIndex, @UsageSetting int usageSetting) {
this.mId = id;
this.mIccId = iccId;
this.mSimSlotIndex = simSlotIndex;
- this.mDisplayName = displayName;
+ this.mDisplayName = displayName;
this.mCarrierName = carrierName;
this.mNameSource = nameSource;
this.mIconTint = iconTint;
this.mNumber = number;
this.mDataRoaming = roaming;
this.mIconBitmap = icon;
- this.mMcc = mcc;
- this.mMnc = mnc;
- this.mCountryIso = countryIso;
+ this.mMcc = TextUtils.emptyIfNull(mcc);
+ this.mMnc = TextUtils.emptyIfNull(mnc);
+ this.mHplmns = null;
+ this.mEhplmns = null;
+ this.mCountryIso = TextUtils.emptyIfNull(countryIso);
this.mIsEmbedded = isEmbedded;
this.mNativeAccessRules = nativeAccessRules;
- this.mCardString = cardString;
+ this.mCardString = TextUtils.emptyIfNull(cardString);
this.mCardId = cardId;
this.mIsOpportunistic = isOpportunistic;
- this.mGroupUUID = groupUUID == null ? null : ParcelUuid.fromString(groupUUID);
+ this.mGroupUuid = groupUuid == null ? null : ParcelUuid.fromString(groupUuid);
this.mIsGroupDisabled = isGroupDisabled;
this.mCarrierId = carrierId;
this.mProfileClass = profileClass;
- this.mSubscriptionType = subType;
- this.mGroupOwner = groupOwner;
+ this.mType = subType;
+ this.mGroupOwner = TextUtils.emptyIfNull(groupOwner);
this.mCarrierConfigAccessRules = carrierConfigAccessRules;
this.mAreUiccApplicationsEnabled = areUiccApplicationsEnabled;
this.mPortIndex = portIndex;
this.mUsageSetting = usageSetting;
}
+
/**
- * @return the subscription ID.
+ * Constructor from builder.
+ *
+ * @param builder Builder of {@link SubscriptionInfo}.
+ */
+ private SubscriptionInfo(@NonNull Builder builder) {
+ this.mId = builder.mId;
+ this.mIccId = builder.mIccId;
+ this.mSimSlotIndex = builder.mSimSlotIndex;
+ this.mDisplayName = builder.mDisplayName;
+ this.mCarrierName = builder.mCarrierName;
+ this.mNameSource = builder.mNameSource;
+ this.mIconTint = builder.mIconTint;
+ this.mNumber = builder.mNumber;
+ this.mDataRoaming = builder.mDataRoaming;
+ this.mIconBitmap = builder.mIconBitmap;
+ this.mMcc = builder.mMcc;
+ this.mMnc = builder.mMnc;
+ this.mEhplmns = builder.mEhplmns;
+ this.mHplmns = builder.mHplmns;
+ this.mCountryIso = builder.mCountryIso;
+ this.mIsEmbedded = builder.mIsEmbedded;
+ this.mNativeAccessRules = builder.mNativeAccessRules;
+ this.mCardString = builder.mCardString;
+ this.mCardId = builder.mCardId;
+ this.mIsOpportunistic = builder.mIsOpportunistic;
+ this.mGroupUuid = builder.mGroupUuid;
+ this.mIsGroupDisabled = builder.mIsGroupDisabled;
+ this.mCarrierId = builder.mCarrierId;
+ this.mProfileClass = builder.mProfileClass;
+ this.mType = builder.mType;
+ this.mGroupOwner = builder.mGroupOwner;
+ this.mCarrierConfigAccessRules = builder.mCarrierConfigAccessRules;
+ this.mAreUiccApplicationsEnabled = builder.mAreUiccApplicationsEnabled;
+ this.mPortIndex = builder.mPortIndex;
+ this.mUsageSetting = builder.mUsageSetting;
+ }
+
+ /**
+ * @return The subscription ID.
*/
public int getSubscriptionId() {
- return this.mId;
+ return mId;
}
/**
@@ -370,78 +439,56 @@
* @return the ICC ID, or an empty string if one of these requirements is not met
*/
public String getIccId() {
- return this.mIccId;
+ return mIccId;
}
/**
- * @hide
- */
- public void clearIccId() {
- this.mIccId = "";
- }
-
- /**
- * @return the slot index of this Subscription's SIM card.
+ * @return The index of the SIM slot that currently contains the subscription and not
+ * necessarily unique and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or
+ * the subscription is inactive.
*/
public int getSimSlotIndex() {
- return this.mSimSlotIndex;
+ return mSimSlotIndex;
}
/**
- * @return the carrier id of this Subscription carrier.
+ * @return The carrier id of this subscription carrier.
+ *
* @see TelephonyManager#getSimCarrierId()
*/
public int getCarrierId() {
- return this.mCarrierId;
+ return mCarrierId;
}
/**
- * @return the name displayed to the user that identifies this subscription
+ * @return The name displayed to the user that identifies this subscription. This name is
+ * used in Settings page and can be renamed by the user.
+ *
+ * @see #getCarrierName()
*/
public CharSequence getDisplayName() {
- return this.mDisplayName;
+ return mDisplayName;
}
/**
- * Sets the name displayed to the user that identifies this subscription
- * @hide
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public void setDisplayName(CharSequence name) {
- this.mDisplayName = name;
- }
-
- /**
- * @return the name displayed to the user that identifies Subscription provider name
+ * @return The name displayed to the user that identifies subscription provider name. This name
+ * is the SPN displayed in status bar and many other places. Can't be renamed by the user.
+ *
+ * @see #getDisplayName()
*/
public CharSequence getCarrierName() {
- return this.mCarrierName;
+ return mCarrierName;
}
/**
- * Sets the name displayed to the user that identifies Subscription provider name
- * @hide
- */
- public void setCarrierName(CharSequence name) {
- this.mCarrierName = name;
- }
-
- /**
- * @return the source of the name, eg NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SPN or
- * NAME_SOURCE_USER_INPUT.
+ * @return The source of the {@link #getCarrierName()}.
+ *
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @SimDisplayNameSource
public int getNameSource() {
- return this.mNameSource;
- }
-
- /**
- * @hide
- */
- public void setAssociatedPlmns(String[] ehplmns, String[] hplmns) {
- mEhplmns = ehplmns;
- mHplmns = hplmns;
+ return mNameSource;
}
/**
@@ -499,15 +546,6 @@
}
/**
- * Sets the color displayed to the user that identifies this subscription
- * @hide
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public void setIconTint(int iconTint) {
- this.mIconTint = iconTint;
- }
-
- /**
* Returns the number of this subscription.
*
* Starting with API level 30, returns the number of this subscription if the calling app meets
@@ -533,28 +571,23 @@
}
/**
- * @hide
- */
- public void clearNumber() {
- mNumber = "";
- }
-
- /**
- * @return the data roaming state for this subscription, either
- * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or {@link SubscriptionManager#DATA_ROAMING_DISABLE}.
+ * Whether user enables data roaming for this subscription or not. Either
+ * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or
+ * {@link SubscriptionManager#DATA_ROAMING_DISABLE}.
*/
public int getDataRoaming() {
- return this.mDataRoaming;
+ return mDataRoaming;
}
/**
- * @return the MCC.
+ * @return The mobile country code.
+ *
* @deprecated Use {@link #getMccString()} instead.
*/
@Deprecated
public int getMcc() {
try {
- return this.mMcc == null ? 0 : Integer.valueOf(this.mMcc);
+ return mMcc == null ? 0 : Integer.parseInt(mMcc);
} catch (NumberFormatException e) {
Log.w(SubscriptionInfo.class.getSimpleName(), "MCC string is not a number");
return 0;
@@ -562,13 +595,14 @@
}
/**
- * @return the MNC.
+ * @return The mobile network code.
+ *
* @deprecated Use {@link #getMncString()} instead.
*/
@Deprecated
public int getMnc() {
try {
- return this.mMnc == null ? 0 : Integer.valueOf(this.mMnc);
+ return mMnc == null ? 0 : Integer.parseInt(mMnc);
} catch (NumberFormatException e) {
Log.w(SubscriptionInfo.class.getSimpleName(), "MNC string is not a number");
return 0;
@@ -576,36 +610,40 @@
}
/**
- * @return The MCC, as a string.
+ * @return The mobile country code.
*/
- public @Nullable String getMccString() {
- return this.mMcc;
+ @Nullable
+ public String getMccString() {
+ return mMcc;
}
/**
- * @return The MNC, as a string.
+ * @return The mobile network code.
*/
- public @Nullable String getMncString() {
- return this.mMnc;
+ @Nullable
+ public String getMncString() {
+ return mMnc;
}
/**
- * @return the ISO country code
+ * @return The ISO country code. Empty if not available.
*/
public String getCountryIso() {
- return this.mCountryIso;
+ return mCountryIso;
}
- /** @return whether the subscription is an eUICC one. */
+ /**
+ * @return {@code true} if the subscription is from eSIM.
+ */
public boolean isEmbedded() {
- return this.mIsEmbedded;
+ return mIsEmbedded;
}
/**
* An opportunistic subscription connects to a network that is
* limited in functionality and / or coverage.
*
- * @return whether subscription is opportunistic.
+ * @return Whether subscription is opportunistic.
*/
public boolean isOpportunistic() {
return mIsOpportunistic;
@@ -617,23 +655,18 @@
* Such that those subscriptions will have some affiliated behaviors such as opportunistic
* subscription may be invisible to the user.
*
- * @return group UUID a String of group UUID if it belongs to a group. Otherwise
- * it will return null.
+ * @return Group UUID a String of group UUID if it belongs to a group. Otherwise
+ * {@code null}.
*/
- public @Nullable ParcelUuid getGroupUuid() {
- return mGroupUUID;
+ @Nullable
+ public ParcelUuid getGroupUuid() {
+ return mGroupUuid;
}
/**
* @hide
*/
- public void clearGroupUuid() {
- this.mGroupUUID = null;
- }
-
- /**
- * @hide
- */
+ @NonNull
public List<String> getEhplmns() {
return mEhplmns == null ? Collections.emptyList() : Arrays.asList(mEhplmns);
}
@@ -641,36 +674,45 @@
/**
* @hide
*/
+ @NonNull
public List<String> getHplmns() {
return mHplmns == null ? Collections.emptyList() : Arrays.asList(mHplmns);
}
/**
- * Return owner package of group the subscription belongs to.
+ * @return The owner package of group the subscription belongs to.
*
* @hide
*/
- public @Nullable String getGroupOwner() {
+ @NonNull
+ public String getGroupOwner() {
return mGroupOwner;
}
/**
- * @return the profile class of this subscription.
+ * @return The profile class populated from the profile metadata if present. Otherwise,
+ * the profile class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no
+ * profile metadata or the subscription is not on an eUICC ({@link #isEmbedded} return
+ * {@code false}).
+ *
* @hide
*/
@SystemApi
- public @SubscriptionManager.ProfileClass int getProfileClass() {
- return this.mProfileClass;
+ @ProfileClass
+ public int getProfileClass() {
+ return mProfileClass;
}
/**
* This method returns the type of a subscription. It can be
* {@link SubscriptionManager#SUBSCRIPTION_TYPE_LOCAL_SIM} or
* {@link SubscriptionManager#SUBSCRIPTION_TYPE_REMOTE_SIM}.
- * @return the type of subscription
+ *
+ * @return The type of the subscription.
*/
- public @SubscriptionManager.SubscriptionType int getSubscriptionType() {
- return mSubscriptionType;
+ @SubscriptionType
+ public int getSubscriptionType() {
+ return mType;
}
/**
@@ -679,7 +721,7 @@
* returns true).
*
* @param context Context of the application to check.
- * @return whether the app is authorized to manage this subscription per its metadata.
+ * @return Whether the app is authorized to manage this subscription per its metadata.
* @hide
* @deprecated - Do not use.
*/
@@ -700,7 +742,7 @@
*/
@Deprecated
public boolean canManageSubscription(Context context, String packageName) {
- List<UiccAccessRule> allAccessRules = getAllAccessRules();
+ List<UiccAccessRule> allAccessRules = getAccessRules();
if (allAccessRules == null) {
return false;
}
@@ -723,27 +765,17 @@
}
/**
- * @return the {@link UiccAccessRule}s that are stored in Uicc, dictating who
- * is authorized to manage this subscription.
- * TODO and fix it properly in R / master: either deprecate this and have 3 APIs
- * native + carrier + all, or have this return all by default.
+ * @return The {@link UiccAccessRule}s that are stored in Uicc, dictating who is authorized to
+ * manage this subscription.
+ *
* @hide
*/
@SystemApi
- public @Nullable List<UiccAccessRule> getAccessRules() {
- if (mNativeAccessRules == null) return null;
- return Arrays.asList(mNativeAccessRules);
- }
-
- /**
- * @return the {@link UiccAccessRule}s that are both stored on Uicc and in carrierConfigs
- * dictating who is authorized to manage this subscription.
- * @hide
- */
- public @Nullable List<UiccAccessRule> getAllAccessRules() {
+ @Nullable
+ public List<UiccAccessRule> getAccessRules() {
List<UiccAccessRule> merged = new ArrayList<>();
if (mNativeAccessRules != null) {
- merged.addAll(getAccessRules());
+ merged.addAll(Arrays.asList(mNativeAccessRules));
}
if (mCarrierConfigAccessRules != null) {
merged.addAll(Arrays.asList(mCarrierConfigAccessRules));
@@ -762,50 +794,38 @@
* href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile
* owner access is deprecated and will be removed in a future release.
*
- * @return the card string of the SIM card which contains the subscription or an empty string
+ * @return The card string of the SIM card which contains the subscription or an empty string
* if these requirements are not met. The card string is the ICCID for UICCs or the EID for
* eUICCs.
+ *
* @hide
- * //TODO rename usages in LPA: UiccSlotUtil.java, UiccSlotsManager.java, UiccSlotInfoTest.java
*/
+ @NonNull
public String getCardString() {
- return this.mCardString;
+ return mCardString;
}
/**
- * @hide
- */
- public void clearCardString() {
- this.mCardString = "";
- }
-
- /**
- * Returns the card ID of the SIM card which contains the subscription (see
- * {@link UiccCardInfo#getCardId()}.
- * @return the cardId
+ * @return The card ID of the SIM card which contains the subscription.
+ *
+ * @see UiccCardInfo#getCardId().
*/
public int getCardId() {
- return this.mCardId;
+ return mCardId;
}
/**
- * Returns the port index of the SIM card which contains the subscription.
- *
- * @return the portIndex
+ * @return The port index of the SIM card which contains the subscription.
*/
public int getPortIndex() {
- return this.mPortIndex;
+ return mPortIndex;
}
/**
- * Set whether the subscription's group is disabled.
- * @hide
- */
- public void setGroupDisabled(boolean isGroupDisabled) {
- this.mIsGroupDisabled = isGroupDisabled;
- }
-
- /**
- * Return whether the subscription's group is disabled.
+ * @return {@code true} if the group of the subscription is disabled. This is only useful if
+ * it's a grouped opportunistic subscription. In this case, if all primary (non-opportunistic)
+ * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile), we
+ * should disable this opportunistic subscription.
+ *
* @hide
*/
@SystemApi
@@ -814,7 +834,7 @@
}
/**
- * Return whether uicc applications are set to be enabled or disabled.
+ * @return {@code true} if Uicc applications are set to be enabled or disabled.
* @hide
*/
@SystemApi
@@ -825,56 +845,50 @@
/**
* Get the usage setting for this subscription.
*
- * @return the usage setting used for this subscription.
+ * @return The usage setting used for this subscription.
*/
- public @UsageSetting int getUsageSetting() {
+ @UsageSetting
+ public int getUsageSetting() {
return mUsageSetting;
}
- public static final @android.annotation.NonNull
- Parcelable.Creator<SubscriptionInfo> CREATOR =
- new Parcelable.Creator<SubscriptionInfo>() {
+ @NonNull
+ public static final Parcelable.Creator<SubscriptionInfo> CREATOR =
+ new Parcelable.Creator<SubscriptionInfo>() {
@Override
public SubscriptionInfo createFromParcel(Parcel source) {
- int id = source.readInt();
- String iccId = source.readString();
- int simSlotIndex = source.readInt();
- CharSequence displayName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
- CharSequence carrierName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
- int nameSource = source.readInt();
- int iconTint = source.readInt();
- String number = source.readString();
- int dataRoaming = source.readInt();
- String mcc = source.readString();
- String mnc = source.readString();
- String countryIso = source.readString();
- boolean isEmbedded = source.readBoolean();
- UiccAccessRule[] nativeAccessRules = source.createTypedArray(UiccAccessRule.CREATOR);
- String cardString = source.readString();
- int cardId = source.readInt();
- int portId = source.readInt();
- boolean isOpportunistic = source.readBoolean();
- String groupUUID = source.readString();
- boolean isGroupDisabled = source.readBoolean();
- int carrierid = source.readInt();
- int profileClass = source.readInt();
- int subType = source.readInt();
- String[] ehplmns = source.createStringArray();
- String[] hplmns = source.createStringArray();
- String groupOwner = source.readString();
- UiccAccessRule[] carrierConfigAccessRules = source.createTypedArray(
- UiccAccessRule.CREATOR);
- boolean areUiccApplicationsEnabled = source.readBoolean();
- int usageSetting = source.readInt();
-
- SubscriptionInfo info = new SubscriptionInfo(id, iccId, simSlotIndex, displayName,
- carrierName, nameSource, iconTint, number, dataRoaming, /* icon= */ null,
- mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, cardId,
- isOpportunistic, groupUUID, isGroupDisabled, carrierid, profileClass, subType,
- groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled,
- portId, usageSetting);
- info.setAssociatedPlmns(ehplmns, hplmns);
- return info;
+ return new Builder()
+ .setId(source.readInt())
+ .setIccId(source.readString())
+ .setSimSlotIndex(source.readInt())
+ .setDisplayName(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source))
+ .setCarrierName(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source))
+ .setNameSource(source.readInt())
+ .setIconTint(source.readInt())
+ .setNumber(source.readString())
+ .setDataRoaming(source.readInt())
+ .setMcc(source.readString())
+ .setMnc(source.readString())
+ .setCountryIso(source.readString())
+ .setEmbedded(source.readBoolean())
+ .setNativeAccessRules(source.createTypedArray(UiccAccessRule.CREATOR))
+ .setCardString(source.readString())
+ .setCardId(source.readInt())
+ .setPortIndex(source.readInt())
+ .setOpportunistic(source.readBoolean())
+ .setGroupUuid(source.readString8())
+ .setGroupDisabled(source.readBoolean())
+ .setCarrierId(source.readInt())
+ .setProfileClass(source.readInt())
+ .setType(source.readInt())
+ .setEhplmns(source.createStringArray())
+ .setHplmns(source.createStringArray())
+ .setGroupOwner(source.readString())
+ .setCarrierConfigAccessRules(source.createTypedArray(
+ UiccAccessRule.CREATOR))
+ .setUiccApplicationsEnabled(source.readBoolean())
+ .setUsageSetting(source.readInt())
+ .build();
}
@Override
@@ -904,11 +918,11 @@
dest.writeInt(mCardId);
dest.writeInt(mPortIndex);
dest.writeBoolean(mIsOpportunistic);
- dest.writeString(mGroupUUID == null ? null : mGroupUUID.toString());
+ dest.writeString8(mGroupUuid == null ? null : mGroupUuid.toString());
dest.writeBoolean(mIsGroupDisabled);
dest.writeInt(mCarrierId);
dest.writeInt(mProfileClass);
- dest.writeInt(mSubscriptionType);
+ dest.writeInt(mType);
dest.writeStringArray(mEhplmns);
dest.writeStringArray(mHplmns);
dest.writeString(mGroupOwner);
@@ -923,6 +937,11 @@
}
/**
+ * Get ICCID stripped PII information on user build.
+ *
+ * @param iccId The original ICCID.
+ * @return The stripped string.
+ *
* @hide
*/
public static String givePrintableIccid(String iccId) {
@@ -951,12 +970,12 @@
+ " nativeAccessRules=" + Arrays.toString(mNativeAccessRules)
+ " cardString=" + cardStringToPrint + " cardId=" + mCardId
+ " portIndex=" + mPortIndex
- + " isOpportunistic=" + mIsOpportunistic + " groupUUID=" + mGroupUUID
+ + " isOpportunistic=" + mIsOpportunistic + " groupUuid=" + mGroupUuid
+ " isGroupDisabled=" + mIsGroupDisabled
+ " profileClass=" + mProfileClass
+ " ehplmns=" + Arrays.toString(mEhplmns)
+ " hplmns=" + Arrays.toString(mHplmns)
- + " subscriptionType=" + mSubscriptionType
+ + " mType=" + mType
+ " groupOwner=" + mGroupOwner
+ " carrierConfigAccessRules=" + Arrays.toString(mCarrierConfigAccessRules)
+ " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled
@@ -966,7 +985,7 @@
@Override
public int hashCode() {
return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
- mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString,
+ mIsOpportunistic, mGroupUuid, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString,
mCardId, mDisplayName, mCarrierName, Arrays.hashCode(mNativeAccessRules),
mIsGroupDisabled, mCarrierId, mProfileClass, mGroupOwner,
mAreUiccApplicationsEnabled, mPortIndex, mUsageSetting);
@@ -974,16 +993,9 @@
@Override
public boolean equals(Object obj) {
- if (obj == null) return false;
- if (obj == this) return true;
-
- SubscriptionInfo toCompare;
- try {
- toCompare = (SubscriptionInfo) obj;
- } catch (ClassCastException ex) {
- return false;
- }
-
+ if (this == obj) return true;
+ if (obj == null || getClass() != obj.getClass()) return false;
+ SubscriptionInfo toCompare = (SubscriptionInfo) obj;
return mId == toCompare.mId
&& mSimSlotIndex == toCompare.mSimSlotIndex
&& mNameSource == toCompare.mNameSource
@@ -994,7 +1006,7 @@
&& mIsGroupDisabled == toCompare.mIsGroupDisabled
&& mAreUiccApplicationsEnabled == toCompare.mAreUiccApplicationsEnabled
&& mCarrierId == toCompare.mCarrierId
- && Objects.equals(mGroupUUID, toCompare.mGroupUUID)
+ && Objects.equals(mGroupUuid, toCompare.mGroupUuid)
&& Objects.equals(mIccId, toCompare.mIccId)
&& Objects.equals(mNumber, toCompare.mNumber)
&& Objects.equals(mMcc, toCompare.mMcc)
@@ -1012,4 +1024,629 @@
&& Arrays.equals(mHplmns, toCompare.mHplmns)
&& mUsageSetting == toCompare.mUsageSetting;
}
+
+ /**
+ * The builder class of {@link SubscriptionInfo}.
+ *
+ * @hide
+ */
+ public static class Builder {
+ /**
+ * The subscription id.
+ */
+ private int mId = 0;
+
+ /**
+ * The ICCID of the SIM that is associated with this subscription, empty if unknown.
+ */
+ @NonNull
+ private String mIccId = "";
+
+ /**
+ * The index of the SIM slot that currently contains the subscription and not necessarily
+ * unique and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or the
+ * subscription is inactive.
+ */
+ private int mSimSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+
+ /**
+ * The name displayed to the user that identifies this subscription. This name is used
+ * in Settings page and can be renamed by the user.
+ */
+ @NonNull
+ private CharSequence mDisplayName = "";
+
+ /**
+ * The name displayed to the user that identifies subscription provider name. This name
+ * is the SPN displayed in status bar and many other places. Can't be renamed by the user.
+ */
+ @NonNull
+ private CharSequence mCarrierName = "";
+
+ /**
+ * The source of the carrier name.
+ */
+ @SimDisplayNameSource
+ private int mNameSource = SubscriptionManager.NAME_SOURCE_CARRIER_ID;
+
+ /**
+ * The color to be used for tinting the icon when displaying to the user.
+ */
+ private int mIconTint = 0;
+
+ /**
+ * The number presented to the user identify this subscription.
+ */
+ @NonNull
+ private String mNumber = "";
+
+ /**
+ * Whether user enables data roaming for this subscription or not. Either
+ * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or
+ * {@link SubscriptionManager#DATA_ROAMING_DISABLE}.
+ */
+ private int mDataRoaming = SubscriptionManager.DATA_ROAMING_DISABLE;
+
+ /**
+ * SIM icon bitmap cache.
+ */
+ @Nullable
+ private Bitmap mIconBitmap = null;
+
+ /**
+ * The mobile country code.
+ */
+ @Nullable
+ private String mMcc = null;
+
+ /**
+ * The mobile network code.
+ */
+ @Nullable
+ private String mMnc = null;
+
+ /**
+ * EHPLMNs associated with the subscription.
+ */
+ @NonNull
+ private String[] mEhplmns = new String[0];
+
+ /**
+ * HPLMNs associated with the subscription.
+ */
+ @NonNull
+ private String[] mHplmns = new String[0];
+
+ /**
+ * The ISO Country code for the subscription's provider.
+ */
+ @NonNull
+ private String mCountryIso = "";
+
+ /**
+ * Whether the subscription is from eSIM.
+ */
+ private boolean mIsEmbedded = false;
+
+ /**
+ * The native access rules for this subscription, if it is embedded and defines any. This
+ * does not include access rules for non-embedded subscriptions.
+ */
+ @Nullable
+ private UiccAccessRule[] mNativeAccessRules = null;
+
+ /**
+ * The card string of the SIM card.
+ */
+ @NonNull
+ private String mCardString = "";
+
+ /**
+ * The card ID of the SIM card which contains the subscription.
+ */
+ private int mCardId = -1;
+
+ /**
+ * Whether the subscription is opportunistic or not.
+ */
+ private boolean mIsOpportunistic = false;
+
+ /**
+ * The group UUID of the subscription group.
+ */
+ @Nullable
+ private ParcelUuid mGroupUuid = null;
+
+ /**
+ * Whether group of the subscription is disabled. This is only useful if it's a grouped
+ * opportunistic subscription. In this case, if all primary (non-opportunistic)
+ * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile),
+ * we should disable this opportunistic subscription.
+ */
+ private boolean mIsGroupDisabled = false;
+
+ /**
+ * The carrier id.
+ *
+ * @see TelephonyManager#getSimCarrierId()
+ */
+ private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+
+ /**
+ * The profile class populated from the profile metadata if present. Otherwise, the profile
+ * class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no profile
+ * metadata or the subscription is not on an eUICC ({@link #isEmbedded} returns
+ * {@code false}).
+ */
+ @ProfileClass
+ private int mProfileClass = SubscriptionManager.PROFILE_CLASS_UNSET;
+
+ /**
+ * The subscription type.
+ */
+ @SubscriptionType
+ private int mType = SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM;
+
+ /**
+ * The owner package of group the subscription belongs to.
+ */
+ @NonNull
+ private String mGroupOwner = "";
+
+ /**
+ * The carrier certificates for this subscription that are saved in carrier configs.
+ * This does not include access rules from the Uicc, whether embedded or non-embedded.
+ */
+ @Nullable
+ private UiccAccessRule[] mCarrierConfigAccessRules = null;
+
+ /**
+ * Whether Uicc applications are configured to enable or not.
+ */
+ private boolean mAreUiccApplicationsEnabled = true;
+
+ /**
+ * the port index of the Uicc card.
+ */
+ private int mPortIndex = 0;
+
+ /**
+ * Subscription's preferred usage setting.
+ */
+ @UsageSetting
+ private int mUsageSetting = SubscriptionManager.USAGE_SETTING_UNKNOWN;
+
+ /**
+ * Default constructor.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Constructor from {@link SubscriptionInfo}.
+ *
+ * @param info The subscription info.
+ */
+ public Builder(@NonNull SubscriptionInfo info) {
+ mId = info.mId;
+ mIccId = info.mIccId;
+ mSimSlotIndex = info.mSimSlotIndex;
+ mDisplayName = info.mDisplayName;
+ mCarrierName = info.mCarrierName;
+ mNameSource = info.mNameSource;
+ mIconTint = info.mIconTint;
+ mNumber = info.mNumber;
+ mDataRoaming = info.mDataRoaming;
+ mIconBitmap = info.mIconBitmap;
+ mMcc = info.mMcc;
+ mMnc = info.mMnc;
+ mEhplmns = info.mEhplmns;
+ mHplmns = info.mHplmns;
+ mCountryIso = info.mCountryIso;
+ mIsEmbedded = info.mIsEmbedded;
+ mNativeAccessRules = info.mNativeAccessRules;
+ mCardString = info.mCardString;
+ mCardId = info.mCardId;
+ mIsOpportunistic = info.mIsOpportunistic;
+ mGroupUuid = info.mGroupUuid;
+ mIsGroupDisabled = info.mIsGroupDisabled;
+ mCarrierId = info.mCarrierId;
+ mProfileClass = info.mProfileClass;
+ mType = info.mType;
+ mGroupOwner = info.mGroupOwner;
+ mCarrierConfigAccessRules = info.mCarrierConfigAccessRules;
+ mAreUiccApplicationsEnabled = info.mAreUiccApplicationsEnabled;
+ mPortIndex = info.mPortIndex;
+ mUsageSetting = info.mUsageSetting;
+ }
+
+ /**
+ * Set the subscription id.
+ *
+ * @param id The subscription id.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setId(int id) {
+ mId = id;
+ return this;
+ }
+
+ /**
+ * Set the ICCID of the SIM that is associated with this subscription.
+ *
+ * @param iccId The ICCID of the SIM that is associated with this subscription.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setIccId(@Nullable String iccId) {
+ mIccId = TextUtils.emptyIfNull(iccId);
+ return this;
+ }
+
+ /**
+ * Set the SIM index of the slot that currently contains the subscription. Set to
+ * {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if the subscription is inactive.
+ *
+ * @param simSlotIndex The SIM slot index.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setSimSlotIndex(int simSlotIndex) {
+ mSimSlotIndex = simSlotIndex;
+ return this;
+ }
+
+ /**
+ * The name displayed to the user that identifies this subscription. This name is used
+ * in Settings page and can be renamed by the user.
+ *
+ * @param displayName The display name.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setDisplayName(@Nullable CharSequence displayName) {
+ mDisplayName = displayName == null ? "" : displayName;
+ return this;
+ }
+
+ /**
+ * The name displayed to the user that identifies subscription provider name. This name
+ * is the SPN displayed in status bar and many other places. Can't be renamed by the user.
+ *
+ * @param carrierName The carrier name.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setCarrierName(@Nullable CharSequence carrierName) {
+ mCarrierName = carrierName == null ? "" : carrierName;
+ return this;
+ }
+
+ /**
+ * Set the source of the carrier name.
+ *
+ * @param nameSource The source of the carrier name.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setNameSource(@SimDisplayNameSource int nameSource) {
+ mNameSource = nameSource;
+ return this;
+ }
+
+ /**
+ * Set the color to be used for tinting the icon when displaying to the user.
+ *
+ * @param iconTint The color to be used for tinting the icon when displaying to the user.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setIconTint(int iconTint) {
+ mIconTint = iconTint;
+ return this;
+ }
+
+ /**
+ * Set the number presented to the user identify this subscription.
+ *
+ * @param number the number presented to the user identify this subscription.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setNumber(@Nullable String number) {
+ mNumber = TextUtils.emptyIfNull(number);
+ return this;
+ }
+
+ /**
+ * Set whether user enables data roaming for this subscription or not.
+ *
+ * @param dataRoaming Data roaming mode. Either
+ * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or
+ * {@link SubscriptionManager#DATA_ROAMING_DISABLE}
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setDataRoaming(int dataRoaming) {
+ mDataRoaming = dataRoaming;
+ return this;
+ }
+
+ /**
+ * Set SIM icon bitmap cache.
+ *
+ * @param iconBitmap SIM icon bitmap cache.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setIcon(@Nullable Bitmap iconBitmap) {
+ mIconBitmap = iconBitmap;
+ return this;
+ }
+
+ /**
+ * Set the mobile country code.
+ *
+ * @param mcc The mobile country code.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setMcc(@Nullable String mcc) {
+ mMcc = mcc;
+ return this;
+ }
+
+ /**
+ * Set the mobile network code.
+ *
+ * @param mnc Mobile network code.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setMnc(@Nullable String mnc) {
+ mMnc = mnc;
+ return this;
+ }
+
+ /**
+ * Set EHPLMNs associated with the subscription.
+ *
+ * @param ehplmns EHPLMNs associated with the subscription.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setEhplmns(@Nullable String[] ehplmns) {
+ mEhplmns = ehplmns == null ? new String[0] : ehplmns;
+ return this;
+ }
+
+ /**
+ * Set HPLMNs associated with the subscription.
+ *
+ * @param hplmns HPLMNs associated with the subscription.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setHplmns(@Nullable String[] hplmns) {
+ mHplmns = hplmns == null ? new String[0] : hplmns;
+ return this;
+ }
+
+ /**
+ * Set the ISO Country code for the subscription's provider.
+ *
+ * @param countryIso The ISO Country code for the subscription's provider.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setCountryIso(@Nullable String countryIso) {
+ mCountryIso = TextUtils.emptyIfNull(countryIso);
+ return this;
+ }
+
+ /**
+ * Set whether the subscription is from eSIM or not.
+ *
+ * @param isEmbedded {@code true} if the subscription is from eSIM.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setEmbedded(boolean isEmbedded) {
+ mIsEmbedded = isEmbedded;
+ return this;
+ }
+
+ /**
+ * Set the native access rules for this subscription, if it is embedded and defines any.
+ * This does not include access rules for non-embedded subscriptions.
+ *
+ * @param nativeAccessRules The native access rules for this subscription.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setNativeAccessRules(@Nullable UiccAccessRule[] nativeAccessRules) {
+ mNativeAccessRules = nativeAccessRules;
+ return this;
+ }
+
+ /**
+ * Set the card string of the SIM card.
+ *
+ * @param cardString The card string of the SIM card.
+ * @return The builder.
+ *
+ * @see #getCardString()
+ */
+ @NonNull
+ public Builder setCardString(@Nullable String cardString) {
+ mCardString = TextUtils.emptyIfNull(cardString);
+ return this;
+ }
+
+ /**
+ * Set the card ID of the SIM card which contains the subscription.
+ *
+ * @param cardId The card ID of the SIM card which contains the subscription.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setCardId(int cardId) {
+ mCardId = cardId;
+ return this;
+ }
+
+ /**
+ * Set whether the subscription is opportunistic or not.
+ *
+ * @param isOpportunistic {@code true} if the subscription is opportunistic.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setOpportunistic(boolean isOpportunistic) {
+ mIsOpportunistic = isOpportunistic;
+ return this;
+ }
+
+ /**
+ * Set the group UUID of the subscription group.
+ *
+ * @param groupUuid The group UUID.
+ * @return The builder.
+ *
+ * @see #getGroupUuid()
+ */
+ @NonNull
+ public Builder setGroupUuid(@Nullable String groupUuid) {
+ mGroupUuid = groupUuid == null ? null : ParcelUuid.fromString(groupUuid);
+ return this;
+ }
+
+ /**
+ * Whether group of the subscription is disabled. This is only useful if it's a grouped
+ * opportunistic subscription. In this case, if all primary (non-opportunistic)
+ * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile),
+ * we should disable this opportunistic subscription.
+ *
+ * @param isGroupDisabled {@code true} if group of the subscription is disabled.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setGroupDisabled(boolean isGroupDisabled) {
+ mIsGroupDisabled = isGroupDisabled;
+ return this;
+ }
+
+ /**
+ * Set the subscription carrier id.
+ *
+ * @param carrierId The carrier id.
+ * @return The builder
+ *
+ * @see TelephonyManager#getSimCarrierId()
+ */
+ @NonNull
+ public Builder setCarrierId(int carrierId) {
+ mCarrierId = carrierId;
+ return this;
+ }
+
+ /**
+ * Set the profile class populated from the profile metadata if present.
+ *
+ * @param profileClass the profile class populated from the profile metadata if present.
+ * @return The builder
+ *
+ * @see #getProfileClass()
+ */
+ @NonNull
+ public Builder setProfileClass(@ProfileClass int profileClass) {
+ mProfileClass = profileClass;
+ return this;
+ }
+
+ /**
+ * Set the subscription type.
+ *
+ * @param type Subscription type.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setType(@SubscriptionType int type) {
+ mType = type;
+ return this;
+ }
+
+ /**
+ * Set the owner package of group the subscription belongs to.
+ *
+ * @param groupOwner Owner package of group the subscription belongs to.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setGroupOwner(@Nullable String groupOwner) {
+ mGroupOwner = TextUtils.emptyIfNull(groupOwner);
+ return this;
+ }
+
+ /**
+ * Set the carrier certificates for this subscription that are saved in carrier configs.
+ * This does not include access rules from the Uicc, whether embedded or non-embedded.
+ *
+ * @param carrierConfigAccessRules The carrier certificates for this subscription
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setCarrierConfigAccessRules(
+ @Nullable UiccAccessRule[] carrierConfigAccessRules) {
+ mCarrierConfigAccessRules = carrierConfigAccessRules;
+ return this;
+ }
+
+ /**
+ * Set whether Uicc applications are configured to enable or not.
+ *
+ * @param uiccApplicationsEnabled {@code true} if Uicc applications are configured to
+ * enable.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setUiccApplicationsEnabled(boolean uiccApplicationsEnabled) {
+ mAreUiccApplicationsEnabled = uiccApplicationsEnabled;
+ return this;
+ }
+
+ /**
+ * Set the port index of the Uicc card.
+ *
+ * @param portIndex The port index of the Uicc card.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setPortIndex(int portIndex) {
+ mPortIndex = portIndex;
+ return this;
+ }
+
+ /**
+ * Set subscription's preferred usage setting.
+ *
+ * @param usageSetting Subscription's preferred usage setting.
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setUsageSetting(@UsageSetting int usageSetting) {
+ mUsageSetting = usageSetting;
+ return this;
+ }
+
+ /**
+ * Build the {@link SubscriptionInfo}.
+ *
+ * @return The {@link SubscriptionInfo} instance.
+ */
+ public SubscriptionInfo build() {
+ return new SubscriptionInfo(this);
+ }
+ }
}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 6bc14bf..87e651b 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1071,6 +1071,13 @@
public static final String ALLOWED_NETWORK_TYPES =
SimInfo.COLUMN_ALLOWED_NETWORK_TYPES_FOR_REASONS;
+ /**
+ * TelephonyProvider column name for user handle associated with a sim.
+ * <P>Type: INTEGER (int)</P>
+ * @hide
+ */
+ public static final String USER_HANDLE = SimInfo.COLUMN_USER_HANDLE;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"USAGE_SETTING_"},
@@ -3099,7 +3106,7 @@
@SystemApi
public boolean canManageSubscription(@NonNull SubscriptionInfo info,
@NonNull String packageName) {
- if (info == null || info.getAllAccessRules() == null || packageName == null) {
+ if (info == null || info.getAccessRules() == null || packageName == null) {
return false;
}
PackageManager packageManager = mContext.getPackageManager();
@@ -3111,7 +3118,7 @@
logd("Unknown package: " + packageName);
return false;
}
- for (UiccAccessRule rule : info.getAllAccessRules()) {
+ for (UiccAccessRule rule : info.getAccessRules()) {
if (rule.getCarrierPrivilegeStatus(packageInfo)
== TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
return true;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 12b4114..b4244dd 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -191,9 +191,6 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
private static final long CALLBACK_ON_MORE_ERROR_CODE_CHANGE = 130595455L;
- // Null IMEI anomaly uuid
- private static final UUID IMEI_ANOMALY_UUID = UUID.fromString(
- "83905f14-6455-450c-be29-8206f0427fe9");
/**
* The key to use when placing the result of {@link #requestModemActivityInfo(ResultReceiver)}
* into the ResultReceiver Bundle.
@@ -2184,11 +2181,7 @@
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
public String getImei() {
- String imei = getImei(getSlotIndex());
- if (imei == null) {
- AnomalyReporter.reportAnomaly(IMEI_ANOMALY_UUID, "getImei: IMEI is null.");
- }
- return imei;
+ return getImei(getSlotIndex());
}
/**
@@ -2231,10 +2224,7 @@
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
public String getImei(int slotIndex) {
ITelephony telephony = getITelephony();
- if (telephony == null) {
- AnomalyReporter.reportAnomaly(IMEI_ANOMALY_UUID, "getImei: telephony is null");
- return null;
- }
+ if (telephony == null) return null;
try {
return telephony.getImeiForSlot(slotIndex, getOpPackageName(), getAttributionTag());
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index 700d615..d8b2cbe 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -725,7 +725,7 @@
@Override
public void onDestroy() {
- mHandlerThread.quit();
+ mHandlerThread.quitSafely();
super.onDestroy();
}
diff --git a/tests/FlickerTests/AndroidManifest.xml b/tests/FlickerTests/AndroidManifest.xml
index e173eba0..487a0c3 100644
--- a/tests/FlickerTests/AndroidManifest.xml
+++ b/tests/FlickerTests/AndroidManifest.xml
@@ -40,6 +40,8 @@
<uses-permission android:name="android.permission.READ_LOGS"/>
<!-- ATM.removeRootTasksWithActivityTypes() -->
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+ <!-- ActivityOptions.makeCustomTaskAnimation() -->
+ <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
<!-- Allow the test to write directly to /sdcard/ -->
<application android:requestLegacyExternalStorage="true">
<uses-library android:name="android.test.runner"/>
diff --git a/services/tests/wmtests/res/values/styles.xml b/tests/FlickerTests/res/anim/show_2000ms.xml
similarity index 62%
rename from services/tests/wmtests/res/values/styles.xml
rename to tests/FlickerTests/res/anim/show_2000ms.xml
index 6857ff99..76e375f 100644
--- a/services/tests/wmtests/res/values/styles.xml
+++ b/tests/FlickerTests/res/anim/show_2000ms.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2022 The Android Open Source Project
~
@@ -14,11 +15,7 @@
~ limitations under the License.
-->
-<resources>
- <style name="WhiteBackgroundTheme" parent="@android:style/Theme.DeviceDefault">
- <item name="android:windowNoTitle">true</item>
- <item name="android:windowFullscreen">true</item>
- <item name="android:windowIsTranslucent">true</item>
- <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
- </style>
-</resources>
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="2000"
+ android:fromXDelta="0"
+ android:toXDelta="0" />
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 1e798f3..f77ec8a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -21,13 +21,14 @@
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import org.junit.Assume
import org.junit.Test
/**
- * Base test class containing common assertions for [ComponentMatcher.NAV_BAR],
- * [ComponentMatcher.TASK_BAR], [ComponentMatcher.STATUS_BAR], and general assertions
+ * Base test class containing common assertions for [ComponentNameMatcher.NAV_BAR],
+ * [ComponentNameMatcher.TASK_BAR], [ComponentNameMatcher.STATUS_BAR], and general assertions
* (layers visible in consecutive states, entire screen covered, etc.)
*/
abstract class BaseTest @JvmOverloads constructor(
@@ -72,7 +73,7 @@
open fun entireScreenCovered() = testSpec.entireScreenCovered()
/**
- * Checks that the [ComponentMatcher.NAV_BAR] layer is visible during the whole transition
+ * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
*
* Note: Phones only
*/
@@ -84,7 +85,8 @@
}
/**
- * Checks the position of the [ComponentMatcher.NAV_BAR] at the start and end of the transition
+ * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the
+ * transition
*
* Note: Phones only
*/
@@ -96,7 +98,7 @@
}
/**
- * Checks that the [ComponentMatcher.NAV_BAR] window is visible during the whole transition
+ * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition
*
* Note: Phones only
*/
@@ -108,8 +110,8 @@
}
/**
- * Checks that the [ComponentMatcher.TASK_BAR] window is visible at the start and end of the
- * transition
+ * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible at the start and end of
+ * the transition
*
* Note: Large screen only
*/
@@ -121,7 +123,8 @@
}
/**
- * Checks that the [ComponentMatcher.TASK_BAR] window is visible during the whole transition
+ * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole
+ * transition
*
* Note: Large screen only
*/
@@ -133,7 +136,7 @@
}
/**
- * Checks that the [ComponentMatcher.STATUS_BAR] layer is visible at the start and end
+ * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible at the start and end
* of the transition
*/
@Presubmit
@@ -142,7 +145,7 @@
testSpec.statusBarLayerIsVisibleAtStartAndEnd()
/**
- * Checks the position of the [ComponentMatcher.STATUS_BAR] at the start and end of the
+ * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
* transition
*/
@Presubmit
@@ -150,7 +153,8 @@
open fun statusBarLayerPositionAtStartAndEnd() = testSpec.statusBarLayerPositionAtStartAndEnd()
/**
- * Checks that the [ComponentMatcher.STATUS_BAR] window is visible during the whole transition
+ * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
+ * transition
*/
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index b8fe9f9..ef5cec2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -33,12 +33,12 @@
import org.junit.Assume.assumeNotNull
class ActivityEmbeddingAppHelper @JvmOverloads constructor(
- instr: Instrumentation,
- launcherName: String = ActivityOptions.ACTIVITY_EMBEDDING_LAUNCHER_NAME,
- component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT,
- launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
- .getInstance(instr)
- .launcherStrategy
+ instr: Instrumentation,
+ launcherName: String = ActivityOptions.ActivityEmbedding.MainActivity.LABEL,
+ component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT,
+ launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
+ .getInstance(instr)
+ .launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
/**
@@ -47,8 +47,8 @@
*/
fun launchPlaceholderSplit(wmHelper: WindowManagerStateHelper) {
val launchButton = uiDevice.wait(
- Until.findObject(By.res(getPackage(), "launch_placeholder_split_button")),
- FIND_TIMEOUT)
+ Until.findObject(By.res(getPackage(), "launch_placeholder_split_button")),
+ FIND_TIMEOUT)
require(launchButton != null) {
"Can't find launch placeholder split button on screen."
}
@@ -62,14 +62,15 @@
companion object {
private const val TAG = "ActivityEmbeddingAppHelper"
- val MAIN_ACTIVITY_COMPONENT = ActivityOptions
- .ACTIVITY_EMBEDDING_MAIN_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
+ val MAIN_ACTIVITY_COMPONENT =
+ ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT.toFlickerComponent()
- val PLACEHOLDER_PRIMARY_COMPONENT = ActivityOptions
- .ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
+ val PLACEHOLDER_PRIMARY_COMPONENT =
+ ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
+ .toFlickerComponent()
- val PLACEHOLDER_SECONDARY_COMPONENT = ActivityOptions
- .ACTIVITY_EMBEDDING_PLACEHOLDER_SECONDARY_ACTIVITY_COMPONENT_NAME
+ val PLACEHOLDER_SECONDARY_COMPONENT =
+ ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT
.toFlickerComponent()
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AppPairsHelper.kt
similarity index 88%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AppPairsHelper.kt
index 826cc2e..4ff4e31 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AppPairsHelper.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.helpers
+package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
import com.android.server.wm.traces.common.ComponentNameMatcher
@@ -23,4 +23,4 @@
instrumentation: Instrumentation,
activityLabel: String,
component: ComponentNameMatcher
-) : BaseAppHelper(instrumentation, activityLabel, component)
+) : StandardAppHelper(instrumentation, activityLabel, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
index b696fc3..132e7b6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
@@ -24,11 +24,11 @@
import com.android.server.wm.traces.parser.toFlickerComponent
class FixedOrientationAppHelper @JvmOverloads constructor(
- instr: Instrumentation,
- launcherName: String = ActivityOptions.PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME,
- component: ComponentNameMatcher =
- ActivityOptions.PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
- .getInstance(instr)
- .launcherStrategy
- ) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+ instr: Instrumentation,
+ launcherName: String = ActivityOptions.PortraitOnlyActivity.LABEL,
+ component: ComponentNameMatcher =
+ ActivityOptions.PortraitOnlyActivity.COMPONENT.toFlickerComponent(),
+ launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
+ .getInstance(instr)
+ .launcherStrategy
+) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
index ea5a5f8..2d81e0d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
@@ -29,9 +29,8 @@
class GameAppHelper @JvmOverloads constructor(
instr: Instrumentation,
- launcherName: String = ActivityOptions.GAME_ACTIVITY_LAUNCHER_NAME,
- component: ComponentNameMatcher =
- ActivityOptions.GAME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
+ launcherName: String = ActivityOptions.Game.LABEL,
+ component: ComponentNameMatcher = ActivityOptions.Game.COMPONENT.toFlickerComponent(),
launcherStrategy: ILauncherStrategy =
LauncherStrategyFactory.getInstance(instr).launcherStrategy,
) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
index e01cceb..b5b0da9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
@@ -32,9 +32,9 @@
instr: Instrumentation,
private val rotation: Int,
private val imePackageName: String = IME_PACKAGE,
- launcherName: String = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_LAUNCHER_NAME,
+ launcherName: String = ActivityOptions.Ime.AutoFocusActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
+ ActivityOptions.Ime.AutoFocusActivity.COMPONENT.toFlickerComponent()
) : ImeAppHelper(instr, launcherName, component) {
override fun openIME(wmHelper: WindowManagerStateHelper) {
// do nothing (the app is focused automatically)
@@ -62,21 +62,22 @@
fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) {
val button = uiDevice.wait(Until.findObject(By.res(getPackage(),
- "start_dialog_themed_activity_btn")), FIND_TIMEOUT)
+ "start_dialog_themed_activity_btn")), FIND_TIMEOUT)
requireNotNull(button) {
"Button not found, this usually happens when the device " +
- "was left in an unknown state (e.g. Screen turned off)"
+ "was left in an unknown state (e.g. Screen turned off)"
}
button.click()
wmHelper.StateSyncBuilder()
.withFullScreenApp(
- ActivityOptions.DIALOG_THEMED_ACTIVITY_COMPONENT_NAME.toFlickerComponent())
+ ActivityOptions.DialogThemedActivity.COMPONENT.toFlickerComponent())
.waitForAndVerify()
}
+
fun dismissDialog(wmHelper: WindowManagerStateHelper) {
val dialog = uiDevice.wait(
- Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT)
+ Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT)
// Pressing back key to dismiss the dialog
if (dialog != null) {
@@ -86,9 +87,10 @@
.waitForAndVerify()
}
}
+
fun getInsetsVisibleFromDialog(type: Int): Boolean {
val insetsVisibilityTextView = uiDevice.wait(
- Until.findObject(By.res("android:id/text1")), FIND_TIMEOUT)
+ Until.findObject(By.res("android:id/text1")), FIND_TIMEOUT)
if (insetsVisibilityTextView != null) {
val visibility = insetsVisibilityTextView.text.toString()
val matcher = when (type) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
index b672b1b..56b6b92 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
@@ -28,12 +28,11 @@
open class ImeAppHelper @JvmOverloads constructor(
instr: Instrumentation,
- launcherName: String = ActivityOptions.IME_ACTIVITY_LAUNCHER_NAME,
+ launcherName: String = ActivityOptions.Ime.Default.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.IME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
- .getInstance(instr)
- .launcherStrategy
+ ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent(),
+ launcherStrategy: ILauncherStrategy =
+ LauncherStrategyFactory.getInstance(instr).launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
/**
* Opens the IME and wait for it to be displayed
@@ -73,8 +72,8 @@
open fun finishActivity(wmHelper: WindowManagerStateHelper) {
val finishButton = uiDevice.wait(
- Until.findObject(By.res(getPackage(), "finish_activity_btn")),
- FIND_TIMEOUT)
+ Until.findObject(By.res(getPackage(), "finish_activity_btn")),
+ FIND_TIMEOUT)
requireNotNull(finishButton) {
"Finish activity button not found, probably IME activity is not on the screen?"
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt
index df47e9d..513103a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt
@@ -26,16 +26,16 @@
class ImeEditorPopupDialogAppHelper @JvmOverloads constructor(
instr: Instrumentation,
- launcherName: String = ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME,
+ launcherName: String = ActivityOptions.Ime.AutoFocusActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
+ ActivityOptions.Ime.AutoFocusActivity.COMPONENT.toFlickerComponent()
) : ImeAppHelper(instr, launcherName, component) {
override fun openIME(wmHelper: WindowManagerStateHelper) {
val editText = uiDevice.wait(Until.findObject(By.text("focused editText")), FIND_TIMEOUT)
- require(editText != null) {
+ requireNotNull(editText) {
"Text field not found, this usually happens when the device " +
- "was left in an unknown state (e.g. in split screen)"
+ "was left in an unknown state (e.g. in split screen)"
}
editText.click()
waitIMEShown(wmHelper)
@@ -43,7 +43,7 @@
fun dismissDialog(wmHelper: WindowManagerStateHelper) {
val dismissButton = uiDevice.wait(
- Until.findObject(By.text("Dismiss")), FIND_TIMEOUT)
+ Until.findObject(By.text("Dismiss")), FIND_TIMEOUT)
// Pressing back key to dismiss the dialog
if (dismissButton != null) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
index d3945c1..3b8d3c3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
@@ -25,10 +25,9 @@
class ImeStateInitializeHelper @JvmOverloads constructor(
instr: Instrumentation,
- launcherName: String = ActivityOptions.IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME,
+ launcherName: String = ActivityOptions.Ime.StateInitializeActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.IME_ACTIVITY_INITIALIZE_COMPONENT_NAME.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
- .getInstance(instr)
- .launcherStrategy
+ ActivityOptions.Ime.StateInitializeActivity.COMPONENT.toFlickerComponent(),
+ launcherStrategy: ILauncherStrategy =
+ LauncherStrategyFactory.getInstance(instr).launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LaunchBubbleHelper.kt
similarity index 70%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LaunchBubbleHelper.kt
index 4d0fbc4..cb54b57 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LaunchBubbleHelper.kt
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.helpers
+package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
+import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.wm.shell.flicker.testapp.Components
-class SimpleAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
+class LaunchBubbleHelper(instrumentation: Instrumentation) : StandardAppHelper(
instrumentation,
- Components.SimpleActivity.LABEL,
- Components.SimpleActivity.COMPONENT.toFlickerComponent()
-)
\ No newline at end of file
+ ActivityOptions.Bubbles.LaunchBubble.LABEL,
+ ActivityOptions.Bubbles.LaunchBubble.COMPONENT.toFlickerComponent()
+)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
index 807e672..dde0b3e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
@@ -28,13 +28,13 @@
import com.android.server.wm.traces.parser.toFlickerComponent
class MailAppHelper @JvmOverloads constructor(
- instr: Instrumentation,
- launcherName: String = ActivityOptions.MAIL_ACTIVITY_LAUNCHER_NAME,
- component: ComponentNameMatcher =
- ActivityOptions.MAIL_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
- .getInstance(instr)
- .launcherStrategy
+ instr: Instrumentation,
+ launcherName: String = ActivityOptions.Mail.LABEL,
+ component: ComponentNameMatcher =
+ ActivityOptions.Mail.COMPONENT.toFlickerComponent(),
+ launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
+ .getInstance(instr)
+ .launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
fun openMail(rowIdx: Int) {
@@ -46,7 +46,7 @@
if (row != null) break
scrollDown()
}
- require(row != null) {""}
+ require(row != null) { "" }
row.click()
uiDevice.wait(Until.gone(By.res(getPackage(), MAIL_LIST_RES_ID)), FIND_TIMEOUT)
}
@@ -57,9 +57,9 @@
}
fun waitForMailList(): UiObject2 {
- var sel = By.res(getPackage(), MAIL_LIST_RES_ID).scrollable(true)
+ val sel = By.res(getPackage(), MAIL_LIST_RES_ID).scrollable(true)
val ret = uiDevice.wait(Until.findObject(sel), FIND_TIMEOUT)
- require(ret != null) {""}
+ requireNotNull(ret) { "Unable to find $MAIL_LIST_RES_ID object" }
return ret
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MultiWindowUtils.kt
similarity index 77%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MultiWindowUtils.kt
index 245a82f..9bdfadb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MultiWindowUtils.kt
@@ -14,20 +14,31 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.helpers
+package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
import android.content.Context
import android.provider.Settings
+import android.util.Log
+import com.android.compatibility.common.util.SystemUtil
import com.android.server.wm.traces.common.ComponentNameMatcher
+import java.io.IOException
-class MultiWindowHelper(
+class MultiWindowUtils(
instrumentation: Instrumentation,
activityLabel: String,
componentsInfo: ComponentNameMatcher
-) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) {
+) : StandardAppHelper(instrumentation, activityLabel, componentsInfo) {
companion object {
+ fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
+ try {
+ SystemUtil.runShellCommand(instrumentation, cmd)
+ } catch (e: IOException) {
+ Log.e(MultiWindowUtils::class.simpleName, "executeShellCommand error! $e")
+ }
+ }
+
fun getDevEnableNonResizableMultiWindow(context: Context): Int =
Settings.Global.getInt(context.contentResolver,
Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
index 9fb574c..f3386af 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
@@ -29,9 +29,9 @@
class NewTasksAppHelper @JvmOverloads constructor(
instr: Instrumentation,
- launcherName: String = ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME,
+ launcherName: String = ActivityOptions.LaunchNewTask.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
+ ActivityOptions.LaunchNewTask.COMPONENT.toFlickerComponent(),
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
@@ -43,7 +43,7 @@
requireNotNull(button) {
"Button not found, this usually happens when the device " +
- "was left in an unknown state (e.g. in split screen)"
+ "was left in an unknown state (e.g. in split screen)"
}
button.click()
wmHelper.StateSyncBuilder()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
index a1dbeea..19ce3ba 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
@@ -25,10 +25,9 @@
class NonResizeableAppHelper @JvmOverloads constructor(
instr: Instrumentation,
- launcherName: String = ActivityOptions.NON_RESIZEABLE_ACTIVITY_LAUNCHER_NAME,
+ launcherName: String = ActivityOptions.NonResizeableActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
- .getInstance(instr)
- .launcherStrategy
+ ActivityOptions.NonResizeableActivity.COMPONENT.toFlickerComponent(),
+ launcherStrategy: ILauncherStrategy =
+ LauncherStrategyFactory.getInstance(instr).launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
index b031a45..97642f1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
@@ -28,25 +28,28 @@
class NotificationAppHelper @JvmOverloads constructor(
instr: Instrumentation,
- launcherName: String = ActivityOptions.NOTIFICATION_ACTIVITY_LAUNCHER_NAME,
+ launcherName: String = ActivityOptions.Notification.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.NOTIFICATION_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
+ ActivityOptions.Notification.COMPONENT.toFlickerComponent(),
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
- .getInstance(instr)
- .launcherStrategy
+ .getInstance(instr)
+ .launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
fun postNotification(wmHelper: WindowManagerStateHelper) {
val button = uiDevice.wait(
- Until.findObject(By.res(getPackage(), "post_notification")),
- FIND_TIMEOUT)
+ Until.findObject(By.res(getPackage(), "post_notification")),
+ FIND_TIMEOUT)
- require(button != null) {
+ requireNotNull(button) {
"Post notification button not found, this usually happens when the device " +
- "was left in an unknown state (e.g. in split screen)"
+ "was left in an unknown state (e.g. in split screen)"
}
button.click()
uiDevice.wait(Until.findObject(By.text("Flicker Test Notification")), FIND_TIMEOUT)
- ?: error("Flicker Notification not found")
+ ?: error("Flicker Notification not found")
+ wmHelper.StateSyncBuilder()
+ .withAppTransitionIdle()
+ .waitForAndVerify()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
similarity index 72%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index bdc05e7..4d801c90 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -14,28 +14,23 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.helpers
+package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
import android.media.session.MediaController
import android.media.session.MediaSessionManager
import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
-import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
+import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.Rect
import com.android.server.wm.traces.common.WindowManagerConditionsFactory
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow
-import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild
-import com.android.wm.shell.flicker.testapp.Components
-class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
+open class PipAppHelper(instrumentation: Instrumentation) : StandardAppHelper(
instrumentation,
- Components.PipActivity.LABEL,
- Components.PipActivity.COMPONENT.toFlickerComponent()
+ ActivityOptions.Pip.LABEL,
+ ActivityOptions.Pip.COMPONENT.toFlickerComponent()
) {
private val mediaSessionManager: MediaSessionManager
get() = context.getSystemService(MediaSessionManager::class.java)
@@ -46,16 +41,11 @@
it.packageName == `package`
}
- fun clickObject(resId: String) {
+ open fun clickObject(resId: String) {
val selector = By.res(`package`, resId)
val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object")
- if (!isTelevision) {
- obj.click()
- } else {
- focusOnObject(selector) || error("Could not focus on `$resId` object")
- uiDevice.pressDPadCenter()
- }
+ obj.click()
}
/**
@@ -85,20 +75,6 @@
fun exitPipToFullScreenViaIntent(wmHelper: WindowManagerStateHelper) =
launchViaIntentAndWaitShown(wmHelper)
- private fun focusOnObject(selector: BySelector): Boolean {
- // We expect all the focusable UI elements to be arranged in a way so that it is possible
- // to "cycle" over all them by clicking the D-Pad DOWN button, going back up to "the top"
- // from "the bottom".
- repeat(FOCUS_ATTEMPTS) {
- uiDevice.findObject(selector)?.apply { if (isFocusedOrHasFocusedChild) return true }
- ?: error("The object we try to focus on is gone.")
-
- uiDevice.pressDPadDown()
- uiDevice.waitForIdle()
- }
- return false
- }
-
fun clickEnterPipButton(wmHelper: WindowManagerStateHelper) {
clickObject(ENTER_PIP_BUTTON_ID)
@@ -140,12 +116,8 @@
"Use PipAppHelper.closePipWindow(wmHelper) instead",
ReplaceWith("closePipWindow(wmHelper)")
)
- fun closePipWindow() {
- if (isTelevision) {
- uiDevice.closeTvPipWindow()
- } else {
- closePipWindow(WindowManagerStateHelper(mInstrumentation))
- }
+ open fun closePipWindow() {
+ closePipWindow(WindowManagerStateHelper(mInstrumentation))
}
private fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect {
@@ -159,20 +131,16 @@
/**
* Taps the pip window and dismisses it by clicking on the X button.
*/
- fun closePipWindow(wmHelper: WindowManagerStateHelper) {
- if (isTelevision) {
- uiDevice.closeTvPipWindow()
- } else {
- val windowRect = getWindowRect(wmHelper)
- uiDevice.click(windowRect.centerX(), windowRect.centerY())
- // search and interact with the dismiss button
- val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
- uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
- val dismissPipObject = uiDevice.findObject(dismissSelector)
- ?: error("PIP window dismiss button not found")
- val dismissButtonBounds = dismissPipObject.visibleBounds
- uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
- }
+ open fun closePipWindow(wmHelper: WindowManagerStateHelper) {
+ val windowRect = getWindowRect(wmHelper)
+ uiDevice.click(windowRect.centerX(), windowRect.centerY())
+ // search and interact with the dismiss button
+ val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
+ uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
+ val dismissPipObject = uiDevice.findObject(dismissSelector)
+ ?: error("PIP window dismiss button not found")
+ val dismissButtonBounds = dismissPipObject.visibleBounds
+ uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
// Wait for animation to complete.
wmHelper.StateSyncBuilder()
@@ -213,7 +181,6 @@
}
companion object {
- private const val FOCUS_ATTEMPTS = 20
private const val ENTER_PIP_BUTTON_ID = "enter_pip"
private const val WITH_CUSTOM_ACTIONS_BUTTON_ID = "with_custom_actions"
private const val MEDIA_SESSION_START_RADIO_BUTTON_ID = "media_session_start"
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
index 6d466d7..fc1ff7c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
@@ -25,10 +25,9 @@
class SeamlessRotationAppHelper @JvmOverloads constructor(
instr: Instrumentation,
- launcherName: String = ActivityOptions.SEAMLESS_ACTIVITY_LAUNCHER_NAME,
+ launcherName: String = ActivityOptions.SeamlessRotation.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
- .getInstance(instr)
- .launcherStrategy
+ ActivityOptions.SeamlessRotation.COMPONENT.toFlickerComponent(),
+ launcherStrategy: ILauncherStrategy =
+ LauncherStrategyFactory.getInstance(instr).launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
index 804ab38..0e1df41 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
@@ -25,10 +25,9 @@
class ShowWhenLockedAppHelper @JvmOverloads constructor(
instr: Instrumentation,
- launcherName: String = ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME,
+ launcherName: String = ActivityOptions.ShowWhenLockedActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
- .getInstance(instr)
- .launcherStrategy
+ ActivityOptions.ShowWhenLockedActivity.COMPONENT.toFlickerComponent(),
+ launcherStrategy: ILauncherStrategy =
+ LauncherStrategyFactory.getInstance(instr).launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
index 5da273a7..e3461a7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
@@ -25,10 +25,9 @@
class SimpleAppHelper @JvmOverloads constructor(
instr: Instrumentation,
- launcherName: String = ActivityOptions.SIMPLE_ACTIVITY_LAUNCHER_NAME,
+ launcherName: String = ActivityOptions.SimpleActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
- .getInstance(instr)
- .launcherStrategy
+ ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent(),
+ launcherStrategy: ILauncherStrategy =
+ LauncherStrategyFactory.getInstance(instr).launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
index 060e9af..f4ea37f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
@@ -29,16 +29,15 @@
class TwoActivitiesAppHelper @JvmOverloads constructor(
instr: Instrumentation,
- launcherName: String = ActivityOptions.BUTTON_ACTIVITY_LAUNCHER_NAME,
+ launcherName: String = ActivityOptions.LaunchNewActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
- .getInstance(instr)
- .launcherStrategy
+ ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent(),
+ launcherStrategy: ILauncherStrategy =
+ LauncherStrategyFactory.getInstance(instr).launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
private val secondActivityComponent =
- ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
+ ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
fun openSecondActivity(device: UiDevice, wmHelper: WindowManagerStateHelper) {
val launchActivityButton = By.res(getPackage(), LAUNCH_SECOND_ACTIVITY)
@@ -46,7 +45,7 @@
requireNotNull(button) {
"Button not found, this usually happens when the device " +
- "was left in an unknown state (e.g. in split screen)"
+ "was left in an unknown state (e.g. in split screen)"
}
button.click()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
index 08d7be2..f019acc 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
@@ -83,19 +83,17 @@
override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/**
- * Checks that the [ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME] activity is visible at
- * the start of the transition, that
- * [ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME] becomes visible during the
- * transition, and that [ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME] is again visible
- * at the end
+ * Checks that the [ActivityOptions.LaunchNewActivity] activity is visible at
+ * the start of the transition, that [ActivityOptions.SimpleActivity] becomes visible during
+ * the transition, and that [ActivityOptions.LaunchNewActivity] is again visible at the end
*/
@Presubmit
@Test
fun finishSubActivity() {
- val buttonActivityComponent = ActivityOptions
- .BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
- val imeAutoFocusActivityComponent = ActivityOptions
- .SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
+ val buttonActivityComponent =
+ ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent()
+ val imeAutoFocusActivityComponent =
+ ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
testSpec.assertWm {
this.isAppWindowOnTop(buttonActivityComponent)
.then()
@@ -106,7 +104,7 @@
}
/**
- * Checks that the [ComponentMatcher.LAUNCHER] window is not on top. The launcher cannot be
+ * Checks that the [ComponentNameMatcher.LAUNCHER] window is not on top. The launcher cannot be
* asserted with `isAppWindowVisible` because it contains 2 windows with the exact same name,
* and both are never simultaneously visible
*/
@@ -119,7 +117,7 @@
}
/**
- * Checks that the [ComponentMatcher.LAUNCHER] layer is never visible during the transition
+ * Checks that the [ComponentNameMatcher.LAUNCHER] layer is never visible during the transition
*/
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
new file mode 100644
index 0000000..4f21412
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.app.Instrumentation
+import android.os.Bundle
+import android.os.Handler
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.R
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
+import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test the [android.app.ActivityOptions.makeCustomTaskAnimation].
+ *
+ * To run this test: `atest FlickerTests:OverrideTaskTransitionTest`
+ *
+ * Actions:
+ * Launches SimpleActivity with alpha_2000ms animation
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group4
+class OverrideTaskTransitionTest(val testSpec: FlickerTestParameter) {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
+
+ @FlickerBuilderProvider
+ fun buildFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ setup {
+ device.wakeUpAndGoToHomeScreen()
+ RemoveAllTasksButHomeRule.removeAllTasksButHome()
+ setRotation(testSpec.startRotation)
+ }
+ transitions {
+ instrumentation.context.startActivity(
+ testApp.openAppIntent, createCustomTaskAnimation())
+ wmHelper.StateSyncBuilder()
+ .add(WindowManagerConditionsFactory.isWMStateComplete())
+ .withAppTransitionIdle()
+ .withWindowSurfaceAppeared(testApp)
+ .waitForAndVerify()
+ }
+ teardown {
+ testApp.exit()
+ }
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun testSimpleActivityIsShownDirectly() {
+ testSpec.assertLayers {
+ isVisible(ComponentNameMatcher.LAUNCHER)
+ .isInvisible(ComponentNameMatcher.SPLASH_SCREEN)
+ .isInvisible(testApp)
+ .then()
+ // The custom animation should block the entire launcher from the very beginning
+ .isInvisible(ComponentNameMatcher.LAUNCHER)
+ }
+ }
+
+ private fun createCustomTaskAnimation(): Bundle {
+ return android.app.ActivityOptions.makeCustomTaskAnimation(instrumentation.context,
+ R.anim.show_2000ms, 0, Handler.getMain(), null, null).toBundle()
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests()
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index 26f46cd..63b78b6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -28,8 +28,7 @@
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.NewTasksAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.testapp.ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME
-import com.android.server.wm.flicker.testapp.ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME
+import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.SPLASH_SCREEN
import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
@@ -105,7 +104,7 @@
}
/**
- * Check that the [ComponentMatcher.LAUNCHER] window is never visible when performing task
+ * Check that the [ComponentNameMatcher.LAUNCHER] window is never visible when performing task
* transitions.
* A solid color background should be shown above it.
*/
@@ -118,7 +117,7 @@
}
/**
- * Checks that the [ComponentMatcher.LAUNCHER] layer is never visible when performing task
+ * Checks that the [ComponentNameMatcher.LAUNCHER] layer is never visible when performing task
* transitions.
* A solid color background should be shown above it.
*/
@@ -246,8 +245,8 @@
companion object {
private val LAUNCH_NEW_TASK_ACTIVITY =
- LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
- private val SIMPLE_ACTIVITY = SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
+ ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent()
+ private val SIMPLE_ACTIVITY = ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
private fun getWallpaperPackage(instrumentation: Instrumentation): IComponentMatcher? {
val wallpaperManager = WallpaperManager.getInstance(instrumentation.targetContext)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 16ad630..9d27079 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -90,7 +90,7 @@
testApp.launchViaIntent(
wmHelper,
stringExtras = mapOf(
- ActivityOptions.EXTRA_STARVE_UI_THREAD
+ ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD
to testSpec.starveUiThread.toString()
)
)
@@ -164,20 +164,23 @@
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. App is full screen")
- override fun statusBarLayerPositionAtStartAndEnd() { }
+ override fun statusBarLayerPositionAtStartAndEnd() {
+ }
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. App is full screen")
- override fun statusBarLayerIsVisibleAtStartAndEnd() { }
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {
+ }
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. App is full screen")
- override fun statusBarWindowIsAlwaysVisible() { }
+ override fun statusBarWindowIsAlwaysVisible() {
+ }
/**
- * Checks that the [ComponentMatcher.STATUS_BAR] window is invisible during the whole
+ * Checks that the [ComponentNameMatcher.STATUS_BAR] window is invisible during the whole
* transition
*/
@Presubmit
@@ -189,7 +192,7 @@
}
/**
- * Checks that the [ComponentMatcher.STATUS_BAR] layer is invisible during the whole
+ * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is invisible during the whole
* transition
*/
@Presubmit
@@ -218,14 +221,17 @@
companion object {
private val FlickerTestParameter.starveUiThread
- get() = config.getOrDefault(ActivityOptions.EXTRA_STARVE_UI_THREAD, false) as Boolean
+ get() = config.getOrDefault(
+ ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD, false) as Boolean
private fun createConfig(
sourceConfig: FlickerTestParameter,
starveUiThread: Boolean
): FlickerTestParameter {
val newConfig = sourceConfig.config.toMutableMap()
- .also { it[ActivityOptions.EXTRA_STARVE_UI_THREAD] = starveUiThread }
+ .also {
+ it[ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD] = starveUiThread
+ }
val nameExt = if (starveUiThread) "_BUSY_UI_THREAD" else ""
return FlickerTestParameter(newConfig, nameOverride = "$sourceConfig$nameExt")
}
@@ -233,8 +239,8 @@
/**
* Creates the test configurations for seamless rotation based on the default rotation
* tests from [FlickerTestParameterFactory.getConfigRotationTests], but adding an
- * additional flag ([ActivityOptions.EXTRA_STARVE_UI_THREAD]) to indicate if the app
- * should starve the UI thread of not
+ * additional flag ([ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD]) to indicate
+ * if the app should starve the UI thread of not
*/
@JvmStatic
private fun getConfigurations(): List<FlickerTestParameter> {
diff --git a/tests/FlickerTests/test-apps/Android.bp b/tests/FlickerTests/test-apps/Android.bp
deleted file mode 100644
index e69de29..0000000
--- a/tests/FlickerTests/test-apps/Android.bp
+++ /dev/null
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 725963b..6e935d1 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -15,41 +15,41 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.server.wm.flicker.testapp">
+ package="com.android.server.wm.flicker.testapp">
<uses-sdk android:minSdkVersion="29"
- android:targetSdkVersion="29"/>
+ android:targetSdkVersion="29"/>
<application android:allowBackup="false"
- android:supportsRtl="true">
+ android:supportsRtl="true">
<uses-library android:name="androidx.window.extensions" android:required="false"/>
<activity android:name=".SimpleActivity"
- android:taskAffinity="com.android.server.wm.flicker.testapp.SimpleActivity"
- android:theme="@style/CutoutShortEdges"
- android:label="SimpleApp"
- android:exported="true">
+ android:taskAffinity="com.android.server.wm.flicker.testapp.SimpleActivity"
+ android:theme="@style/CutoutShortEdges"
+ android:label="SimpleActivity"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".ImeActivity"
- android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivity"
- android:theme="@style/CutoutShortEdges"
- android:label="ImeApp"
- android:exported="true">
+ android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivity"
+ android:theme="@style/CutoutShortEdges"
+ android:label="ImeActivity"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".ImeActivityAutoFocus"
- android:theme="@style/CutoutShortEdges"
- android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus"
- android:windowSoftInputMode="stateVisible"
- android:configChanges="orientation|screenSize"
- android:label="ImeAppAutoFocus"
- android:exported="true">
+ android:theme="@style/CutoutShortEdges"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus"
+ android:windowSoftInputMode="stateVisible"
+ android:configChanges="orientation|screenSize"
+ android:label="ImeAppAutoFocus"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@@ -66,34 +66,34 @@
</intent-filter>
</activity>
<activity android:name=".SeamlessRotationActivity"
- android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity"
- android:theme="@style/CutoutShortEdges"
- android:configChanges="orientation|screenSize"
- android:label="SeamlessApp"
- android:exported="true">
+ android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity"
+ android:theme="@style/CutoutShortEdges"
+ android:configChanges="orientation|screenSize"
+ android:label="SeamlessActivity"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".NonResizeableActivity"
- android:theme="@style/CutoutShortEdges"
- android:resizeableActivity="false"
- android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableActivity"
- android:label="NonResizeableApp"
- android:exported="true"
- android:showOnLockScreen="true">
+ android:theme="@style/CutoutShortEdges"
+ android:resizeableActivity="false"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableActivity"
+ android:label="NonResizeableActivity"
+ android:exported="true"
+ android:showOnLockScreen="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
- <activity android:name=".ButtonActivity"
- android:taskAffinity="com.android.server.wm.flicker.testapp.ButtonActivity"
- android:theme="@style/CutoutShortEdges"
- android:configChanges="orientation|screenSize"
- android:label="ButtonActivity"
- android:exported="true">
+ <activity android:name=".LaunchNewActivity"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewActivity"
+ android:theme="@style/CutoutShortEdges"
+ android:configChanges="orientation|screenSize"
+ android:label="LaunchNewActivity"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@@ -111,55 +111,56 @@
</intent-filter>
</activity>
<activity android:name=".DialogThemedActivity"
- android:taskAffinity="com.android.server.wm.flicker.testapp.DialogThemedActivity"
- android:configChanges="orientation|screenSize"
- android:theme="@style/DialogTheme"
- android:label="DialogThemedActivity"
- android:exported="true">
+ android:taskAffinity="com.android.server.wm.flicker.testapp.DialogThemedActivity"
+ android:configChanges="orientation|screenSize"
+ android:theme="@style/DialogTheme"
+ android:label="DialogThemedActivity"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".PortraitOnlyActivity"
- android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitOnlyActivity"
- android:theme="@style/CutoutShortEdges"
- android:screenOrientation="portrait"
- android:configChanges="orientation|screenSize"
- android:exported="true">
+ android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitOnlyActivity"
+ android:theme="@style/CutoutShortEdges"
+ android:screenOrientation="portrait"
+ android:configChanges="orientation|screenSize"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".ImeEditorPopupDialogActivity"
- android:taskAffinity="com.android.server.wm.flicker.testapp.ImeEditorPopupDialogActivity"
- android:configChanges="orientation|screenSize"
- android:theme="@style/CutoutShortEdges"
- android:label="ImeEditorPopupDialogActivity"
- android:exported="true">
+ android:taskAffinity="com.android.server.wm.flicker.testapp.ImeEditorPopupDialogActivity"
+ android:configChanges="orientation|screenSize"
+ android:theme="@style/CutoutShortEdges"
+ android:label="ImeEditorPopupDialogActivity"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".ShowWhenLockedActivity"
- android:taskAffinity="com.android.server.wm.flicker.testapp.ShowWhenLockedActivity"
- android:theme="@style/CutoutShortEdges"
- android:configChanges="orientation|screenSize"
- android:label="ShowWhenLockedActivity"
- android:showWhenLocked="true"
- android:exported="true">
+ android:taskAffinity="com.android.server.wm.flicker.testapp.ShowWhenLockedActivity"
+ android:theme="@style/CutoutShortEdges"
+ android:configChanges="orientation|screenSize"
+ android:label="ShowWhenLockedActivity"
+ android:showWhenLocked="true"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".NotificationActivity"
- android:taskAffinity="com.android.server.wm.flicker.testapp.NotificationActivity"
- android:theme="@style/CutoutShortEdges"
- android:configChanges="orientation|screenSize"
- android:exported="true">
+ android:taskAffinity="com.android.server.wm.flicker.testapp.NotificationActivity"
+ android:theme="@style/CutoutShortEdges"
+ android:configChanges="orientation|screenSize"
+ android:label="NotificationActivity"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@@ -173,8 +174,8 @@
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
@@ -193,42 +194,110 @@
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="false"/>
<activity android:name=".MailActivity"
- android:exported="true"
- android:label="MailApp"
- android:taskAffinity="com.android.server.wm.flicker.testapp.MailActivity"
- android:theme="@style/Theme.AppCompat.Light">
+ android:exported="true"
+ android:label="MailActivity"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.MailActivity"
+ android:theme="@style/Theme.AppCompat.Light">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".GameActivity"
- android:taskAffinity="com.android.server.wm.flicker.testapp.GameActivity"
- android:immersive="true"
- android:theme="@android:style/Theme.NoTitleBar"
- android:configChanges="screenSize"
- android:label="GameApp"
- android:exported="true">
+ android:taskAffinity="com.android.server.wm.flicker.testapp.GameActivity"
+ android:immersive="true"
+ android:theme="@android:style/Theme.NoTitleBar"
+ android:configChanges="screenSize"
+ android:label="GameActivity"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".PipActivity"
+ android:resizeableActivity="true"
+ android:supportsPictureInPicture="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.PipActivity"
+ android:theme="@style/CutoutShortEdges"
+ android:launchMode="singleTop"
+ android:label="PipActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".SplitScreenActivity"
+ android:resizeableActivity="true"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenActivity"
+ android:theme="@style/CutoutShortEdges"
+ android:label="SplitScreenPrimaryActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".SplitScreenSecondaryActivity"
+ android:resizeableActivity="true"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenSecondaryActivity"
+ android:theme="@style/CutoutShortEdges"
+ android:label="SplitScreenSecondaryActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".SendNotificationActivity"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.SendNotificationActivity"
+ android:theme="@style/CutoutShortEdges"
+ android:label="SendNotificationActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".LaunchBubbleActivity"
+ android:label="LaunchBubbleActivity"
+ android:exported="true"
+ android:theme="@style/CutoutShortEdges"
+ android:launchMode="singleTop">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".BubbleActivity"
+ android:label="BubbleActivity"
+ android:exported="false"
+ android:theme="@style/CutoutShortEdges"
+ android:resizeableActivity="true"/>
<service
android:name=".AssistantInteractionSessionService"
android:exported="true"
- android:permission="android.permission.BIND_VOICE_INTERACTION" />
+ android:permission="android.permission.BIND_VOICE_INTERACTION"/>
<service
android:name=".AssistantRecognitionService"
android:exported="true"
android:label="Test Voice Interaction Service">
<intent-filter>
- <action android:name="android.speech.RecognitionService" />
- <category android:name="android.intent.category.DEFAULT" />
+ <action android:name="android.speech.RecognitionService"/>
+ <category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<meta-data
android:name="android.speech"
- android:resource="@xml/recognition_service" />
+ android:resource="@xml/recognition_service"/>
</service>
<service
android:name=".AssistantInteractionService"
@@ -236,12 +305,12 @@
android:label="Test Voice Interaction Service"
android:permission="android.permission.BIND_VOICE_INTERACTION">
<intent-filter>
- <action android:name="android.service.voice.VoiceInteractionService" />
+ <action android:name="android.service.voice.VoiceInteractionService"/>
</intent-filter>
<meta-data
android:name="android.voice_interaction"
- android:resource="@xml/interaction_service" />
+ android:resource="@xml/interaction_service"/>
</service>
</application>
- <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
+ <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>
</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png b/tests/FlickerTests/test-apps/flickerapp/res/drawable/bg.png
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png
rename to tests/FlickerTests/test-apps/flickerapp/res/drawable/bg.png
Binary files differ
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_bubble.xml b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_bubble.xml
new file mode 100644
index 0000000..4ea156d
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_bubble.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7.2,14.4m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14.8,18m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.2,8.8m-4.8,0a4.8,4.8 0,1 1,9.6 0a4.8,4.8 0,1 1,-9.6 0"/>
+</vector>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_message.xml b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_message.xml
new file mode 100644
index 0000000..45ed98c
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_message.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,4c-4.97,0 -9,3.58 -9,8c0,1.53 0.49,2.97 1.33,4.18c0.12,0.18 0.2,0.46 0.1,0.66c-0.33,0.68 -0.79,1.52 -1.38,2.39c-0.12,0.17 0.01,0.41 0.21,0.39c0.63,-0.05 1.86,-0.26 3.38,-0.91c0.17,-0.07 0.36,-0.06 0.52,0.03C8.55,19.54 10.21,20 12,20c4.97,0 9,-3.58 9,-8S16.97,4 12,4zM16.94,11.63l-3.29,3.29c-0.13,0.13 -0.34,0.04 -0.34,-0.14v-1.57c0,-0.11 -0.1,-0.21 -0.21,-0.2c-2.19,0.06 -3.65,0.65 -5.14,1.95c-0.15,0.13 -0.38,0 -0.33,-0.19c0.7,-2.57 2.9,-4.57 5.5,-4.75c0.1,-0.01 0.18,-0.09 0.18,-0.19V8.2c0,-0.18 0.22,-0.27 0.34,-0.14l3.29,3.29C17.02,11.43 17.02,11.55 16.94,11.63z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml
similarity index 65%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml
rename to tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml
index f8b0ca3..7c7b2ca 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml
@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright 2021 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.
--->
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_button.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml
similarity index 100%
rename from tests/FlickerTests/test-apps/flickerapp/res/layout/activity_button.xml
rename to tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_main.xml
similarity index 67%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml
rename to tests/FlickerTests/test-apps/flickerapp/res/layout/activity_main.xml
index f23c464..553c7fe0 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_main.xml
@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright 2021 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.
--->
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_notification.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_notification.xml
new file mode 100644
index 0000000..7c11984
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_notification.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@android:color/black">
+
+ <Button
+ android:id="@+id/button_send_notification"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:text="Send Notification" />
+</RelativeLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
similarity index 87%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
rename to tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
index 2290983..f7ba45b 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml
new file mode 100644
index 0000000..79e88e4
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@android:color/holo_green_light">
+
+ <TextView
+ android:id="@+id/SplitScreenTest"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:gravity="center_vertical|center_horizontal"
+ android:textIsSelectable="true"
+ android:text="PrimaryActivity"
+ android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml
new file mode 100644
index 0000000..ed9feaf
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@android:color/holo_blue_light">
+
+ <TextView
+ android:id="@+id/SplitScreenTest"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:gravity="center_vertical|center_horizontal"
+ android:text="SecondaryActivity"
+ android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
index 166e3ca..04a590d 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
@@ -16,9 +16,6 @@
package com.android.server.wm.flicker.testapp;
-import static com.android.server.wm.flicker.testapp.ActivityOptions.ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME;
-import static com.android.server.wm.flicker.testapp.ActivityOptions.ACTIVITY_EMBEDDING_PLACEHOLDER_SECONDARY_ACTIVITY_COMPONENT_NAME;
-
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
@@ -39,8 +36,6 @@
private static final String TAG = "ActivityEmbeddingMainActivity";
private static final float DEFAULT_SPLIT_RATIO = 0.5f;
- private ActivityEmbeddingComponent mEmbeddingComponent;
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -51,20 +46,24 @@
/** R.id.launch_placeholder_split_button onClick */
public void launchPlaceholderSplit(View view) {
- startActivity(new Intent().setComponent(
- ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME));
+ startActivity(
+ new Intent().setComponent(
+ ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
+ )
+ );
}
private void initializeSplitRules() {
- mEmbeddingComponent = ActivityEmbeddingAppHelper.getActivityEmbeddingComponent();
- if (mEmbeddingComponent == null) {
+ ActivityEmbeddingComponent embeddingComponent =
+ ActivityEmbeddingAppHelper.getActivityEmbeddingComponent();
+ if (embeddingComponent == null) {
// Embedding not supported
Log.d(TAG, "ActivityEmbedding is not supported on this device");
finish();
return;
}
- mEmbeddingComponent.setEmbeddingRules(getSplitRules());
+ embeddingComponent.setEmbeddingRules(getSplitRules());
}
private Set<EmbeddingRule> getSplitRules() {
@@ -72,10 +71,10 @@
final SplitPlaceholderRule placeholderRule = new SplitPlaceholderRule.Builder(
new Intent().setComponent(
- ACTIVITY_EMBEDDING_PLACEHOLDER_SECONDARY_ACTIVITY_COMPONENT_NAME),
+ ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT),
activity -> activity instanceof ActivityEmbeddingPlaceholderPrimaryActivity,
intent -> intent.getComponent().equals(
- ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME),
+ ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT),
windowMetrics -> true)
.setSplitRatio(DEFAULT_SPLIT_RATIO)
.build();
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index cae3df4..9583f97 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -19,94 +19,181 @@
import android.content.ComponentName;
public class ActivityOptions {
- public static final String EXTRA_STARVE_UI_THREAD = "StarveUiThread";
public static final String FLICKER_APP_PACKAGE = "com.android.server.wm.flicker.testapp";
- public static final String SEAMLESS_ACTIVITY_LAUNCHER_NAME = "SeamlessApp";
- public static final ComponentName SEAMLESS_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".SeamlessRotationActivity");
+ public static class SimpleActivity {
+ public static final String LABEL = "SimpleActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".SimpleActivity");
+ }
- public static final String IME_ACTIVITY_AUTO_FOCUS_LAUNCHER_NAME = "ImeAppAutoFocus";
- public static final ComponentName IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".ImeActivityAutoFocus");
+ public static class SeamlessRotation {
+ public static final String LABEL = "SeamlessRotationActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".SeamlessRotationActivity");
- public static final String IME_ACTIVITY_LAUNCHER_NAME = "ImeActivity";
- public static final ComponentName IME_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
+ public static final String EXTRA_STARVE_UI_THREAD = "StarveUiThread";
+ }
+
+ public static class Ime {
+ public static class Default {
+ public static final String LABEL = "ImeActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
FLICKER_APP_PACKAGE + ".ImeActivity");
+ }
- public static final String IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME = "ImeStateInitializeActivity";
- public static final ComponentName IME_ACTIVITY_INITIALIZE_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
+ public static class AutoFocusActivity {
+ public static final String LABEL = "ImeAppAutoFocus";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".ImeActivityAutoFocus");
+ }
+
+ public static class StateInitializeActivity {
+ public static final String LABEL = "ImeStateInitializeActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
FLICKER_APP_PACKAGE + ".ImeStateInitializeActivity");
+ }
- public static final String SIMPLE_ACTIVITY_LAUNCHER_NAME = "SimpleApp";
- public static final ComponentName SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".SimpleActivity");
-
- public static final String NON_RESIZEABLE_ACTIVITY_LAUNCHER_NAME = "NonResizeableApp";
- public static final ComponentName NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".NonResizeableActivity");
-
- public static final String BUTTON_ACTIVITY_LAUNCHER_NAME = "ButtonApp";
- public static final ComponentName BUTTON_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".ButtonActivity");
-
- public static final String LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME = "LaunchNewTaskApp";
- public static final ComponentName LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity");
-
- public static final String DIALOG_THEMED_ACTIVITY = "DialogThemedActivity";
- public static final ComponentName DIALOG_THEMED_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".DialogThemedActivity");
-
- public static final String PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME = "PortraitOnlyActivity";
- public static final ComponentName PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".PortraitOnlyActivity");
-
- public static final String EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME =
- "ImeEditorPopupDialogActivity";
- public static final ComponentName EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
+ public static class EditorPopupDialogActivity {
+ public static final String LABEL = "ImeEditorPopupDialogActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
FLICKER_APP_PACKAGE + ".ImeEditorPopupDialogActivity");
+ }
+ }
- public static final String SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME = "ShowWhenLockedApp";
- public static final ComponentName SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity");
+ public static class NonResizeableActivity {
+ public static final String LABEL = "NonResizeableActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".NonResizeableActivity");
+ }
- public static final String NOTIFICATION_ACTIVITY_LAUNCHER_NAME = "NotificationApp";
- public static final ComponentName NOTIFICATION_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".NotificationActivity");
+ public static class DialogThemedActivity {
+ public static final String LABEL = "DialogThemedActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".DialogThemedActivity");
+ }
- public static final String ACTIVITY_EMBEDDING_LAUNCHER_NAME = "ActivityEmbeddingMainActivity";
- public static final ComponentName ACTIVITY_EMBEDDING_MAIN_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
+ public static class PortraitOnlyActivity {
+ public static final String LABEL = "PortraitOnlyActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".PortraitOnlyActivity");
+ public static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
+ }
+
+ public static class ActivityEmbedding {
+ public static class MainActivity {
+ public static final String LABEL = "ActivityEmbeddingMainActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
FLICKER_APP_PACKAGE + ".ActivityEmbeddingMainActivity");
- public static final ComponentName
- ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME = new ComponentName(
- FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderPrimaryActivity");
- public static final ComponentName
- ACTIVITY_EMBEDDING_PLACEHOLDER_SECONDARY_ACTIVITY_COMPONENT_NAME = new ComponentName(
- FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderSecondaryActivity");
+ }
- public static final String MAIL_ACTIVITY_LAUNCHER_NAME = "MailActivity";
- public static final ComponentName MAIL_ACTIVITY_COMPONENT_NAME = new ComponentName(
- FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".MailActivity");
+ public static class PlaceholderPrimaryActivity {
+ public static final String LABEL = "ActivityEmbeddingPlaceholderPrimaryActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderPrimaryActivity");
+ }
- public static final String GAME_ACTIVITY_LAUNCHER_NAME = "GameApp";
- public static final ComponentName GAME_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".GameActivity");
+ public static class PlaceholderSecondaryActivity {
+ public static final String LABEL = "ActivityEmbeddingPlaceholderSecondaryActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderSecondaryActivity");
+ }
+ }
+
+ public static class Notification {
+ public static final String LABEL = "NotificationActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".NotificationActivity");
+ }
+
+ public static class Mail {
+ public static final String LABEL = "MailActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".MailActivity");
+ }
+
+ public static class ShowWhenLockedActivity {
+ public static final String LABEL = "ShowWhenLockedActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity");
+ }
+
+ public static class LaunchNewTask {
+ public static final String LABEL = "LaunchNewTaskActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity");
+ }
+
+ public static class Game {
+ public static final String LABEL = "GameActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".GameActivity");
+ }
+
+ public static class LaunchNewActivity {
+ public static final String LABEL = "LaunchNewActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".LaunchNewActivity");
+ }
+
+ public static class Pip {
+ // Test App > Pip Activity
+ public static final String LABEL = "PipActivity";
+ public static final String MENU_ACTION_NO_OP = "No-Op";
+ public static final String MENU_ACTION_ON = "On";
+ public static final String MENU_ACTION_OFF = "Off";
+ public static final String MENU_ACTION_CLEAR = "Clear";
+
+ // Intent action that this activity dynamically registers to enter picture-in-picture
+ public static final String ACTION_ENTER_PIP =
+ FLICKER_APP_PACKAGE + ".PipActivity.ENTER_PIP";
+ // Intent action that this activity dynamically registers to set requested orientation.
+ // Will apply the oriention to the value set in the EXTRA_FIXED_ORIENTATION extra.
+ public static final String ACTION_SET_REQUESTED_ORIENTATION =
+ FLICKER_APP_PACKAGE + ".PipActivity.SET_REQUESTED_ORIENTATION";
+
+ // Calls enterPictureInPicture() on creation
+ public static final String EXTRA_ENTER_PIP = "enter_pip";
+ // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
+ public static final String EXTRA_PIP_ORIENTATION = "fixed_orientation";
+ // Adds a click listener to finish this activity when it is clicked
+ public static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
+
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".PipActivity");
+ }
+
+ public static class SplitScreen {
+ public static class Primary {
+ public static final String LABEL = "SplitScreenPrimaryActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".SplitScreenActivity");
+ }
+
+ public static class Secondary {
+ public static final String LABEL = "SplitScreenSecondaryActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".SplitScreenSecondaryActivity");
+ }
+ }
+
+ public static class SendNotificationActivity {
+ public static final String LABEL = "SendNotificationActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".SendNotificationActivity");
+ }
+
+ public static class Bubbles {
+ public static class LaunchBubble {
+ public static final String LABEL = "LaunchBubbleActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".LaunchBubbleActivity");
+ }
+
+ public static class BubbleActivity {
+ public static final String LABEL = "BubbleActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".BubbleActivity");
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleActivity.java
similarity index 94%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleActivity.java
index bc3bc75..58d7e67 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
import android.app.Activity;
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java
index 6cd93ef..c92b82b 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
import android.app.Notification;
@@ -86,7 +86,7 @@
}
- private int getNextNotifyId() {
+ private int getNextNotifyId() {
int id = mNextNotifyId;
mNextNotifyId++;
return id;
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/FixedActivity.java
similarity index 82%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/FixedActivity.java
index d4ae6c1..722929f 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/FixedActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
-import static com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION;
import android.os.Bundle;
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java
index 71fa66d..dea3444 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
import android.app.Activity;
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java
similarity index 87%
rename from tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java
index b42ac2a..e5710c8 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java
@@ -22,7 +22,7 @@
import android.view.WindowManager;
import android.widget.Button;
-public class ButtonActivity extends Activity {
+public class LaunchNewActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -30,11 +30,11 @@
p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(p);
- setContentView(R.layout.activity_button);
+ setContentView(R.layout.activity_launch_new);
Button button = findViewById(R.id.launch_second_activity);
button.setOnClickListener(v -> {
- Intent intent = new Intent(ButtonActivity.this, SimpleActivity.class);
+ Intent intent = new Intent(LaunchNewActivity.this, SimpleActivity.class);
startActivity(intent);
});
}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
similarity index 95%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
index 615b173..cdb1d42 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
import static android.media.MediaMetadata.METADATA_KEY_TITLE;
import static android.media.session.PlaybackState.ACTION_PAUSE;
@@ -24,10 +24,10 @@
import static android.media.session.PlaybackState.STATE_PLAYING;
import static android.media.session.PlaybackState.STATE_STOPPED;
-import static com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP;
-import static com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
-import static com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP;
-import static com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_PIP_ORIENTATION;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_SET_REQUESTED_ORIENTATION;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.EXTRA_ENTER_PIP;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.EXTRA_PIP_ORIENTATION;
import android.app.Activity;
import android.app.PendingIntent;
@@ -127,7 +127,6 @@
break;
default:
Log.w(TAG, "Unhandled action=" + intent.getAction());
- return;
}
}
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java
index 5cf81cb..ce7a005 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java
@@ -18,7 +18,7 @@
import static android.os.SystemClock.sleep;
-import static com.android.server.wm.flicker.testapp.ActivityOptions.EXTRA_STARVE_UI_THREAD;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD;
import android.app.Activity;
import android.os.Bundle;
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SendNotificationActivity.java
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SendNotificationActivity.java
index 8020ef2..8868488 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SendNotificationActivity.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
import android.app.Activity;
import android.app.Notification;
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java
similarity index 88%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java
index 9c82eea..70196ae 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
import android.app.Activity;
import android.os.Bundle;
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenSecondaryActivity.java
similarity index 89%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenSecondaryActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenSecondaryActivity.java
index baa1e6f..a8ce8ff 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenSecondaryActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenSecondaryActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
import android.app.Activity;
import android.os.Bundle;
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 54b3c40..f924b2e 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -23,6 +23,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE;
import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
@@ -276,7 +277,6 @@
@Test
public void testSystemReady() throws Exception {
mVcnMgmtSvc.systemReady();
- mTestLooper.dispatchAll();
verify(mConnMgr).registerNetworkProvider(any(VcnNetworkProvider.class));
verify(mSubscriptionTracker).register();
@@ -494,8 +494,10 @@
mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
triggerSubscriptionTrackerCbAndGetSnapshot(null, Collections.emptySet());
- mTestLooper.dispatchAll();
+ // Verify teardown after delay
+ mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+ mTestLooper.dispatchAll();
verify(vcn).teardownAsynchronously();
verify(mMockPolicyListener).onPolicyChanged();
}
@@ -521,6 +523,92 @@
assertEquals(0, mVcnMgmtSvc.getAllVcns().size());
}
+ /**
+ * Tests an intermediate state where carrier privileges are marked as lost before active data
+ * subId changes during a SIM ejection.
+ *
+ * <p>The expected outcome is that the VCN is torn down after a delay, as opposed to
+ * immediately.
+ */
+ @Test
+ public void testTelephonyNetworkTrackerCallbackLostCarrierPrivilegesBeforeActiveDataSubChanges()
+ throws Exception {
+ setupActiveSubscription(TEST_UUID_2);
+
+ final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
+ final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
+
+ // Simulate privileges lost
+ triggerSubscriptionTrackerCbAndGetSnapshot(
+ TEST_SUBSCRIPTION_ID,
+ TEST_UUID_2,
+ Collections.emptySet(),
+ Collections.emptyMap(),
+ false /* hasCarrierPrivileges */);
+
+ // Verify teardown after delay
+ mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+ mTestLooper.dispatchAll();
+ verify(vcn).teardownAsynchronously();
+ }
+
+ @Test
+ public void testTelephonyNetworkTrackerCallbackSimSwitchesDoNotKillVcnInstances()
+ throws Exception {
+ setupActiveSubscription(TEST_UUID_2);
+
+ final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
+ final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
+
+ // Simulate SIM unloaded
+ triggerSubscriptionTrackerCbAndGetSnapshot(
+ INVALID_SUBSCRIPTION_ID,
+ null /* activeDataSubscriptionGroup */,
+ Collections.emptySet(),
+ Collections.emptyMap(),
+ false /* hasCarrierPrivileges */);
+
+ // Simulate new SIM loaded right during teardown delay.
+ mTestLooper.moveTimeForward(
+ VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
+ mTestLooper.dispatchAll();
+ triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_2, Collections.singleton(TEST_UUID_2));
+
+ // Verify that even after the full timeout duration, the VCN instance is not torn down
+ mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+ mTestLooper.dispatchAll();
+ verify(vcn, never()).teardownAsynchronously();
+ }
+
+ @Test
+ public void testTelephonyNetworkTrackerCallbackDoesNotKillNewVcnInstances() throws Exception {
+ setupActiveSubscription(TEST_UUID_2);
+
+ final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
+ final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2);
+
+ // Simulate SIM unloaded
+ triggerSubscriptionTrackerCbAndGetSnapshot(null, Collections.emptySet());
+
+ // Config cleared, SIM reloaded & config re-added right before teardown delay, staring new
+ // vcnInstance.
+ mTestLooper.moveTimeForward(
+ VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
+ mTestLooper.dispatchAll();
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME);
+ triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_2, Collections.singleton(TEST_UUID_2));
+ final Vcn newInstance = startAndGetVcnInstance(TEST_UUID_2);
+
+ // Verify that new instance was different, and the old one was torn down
+ assertTrue(oldInstance != newInstance);
+ verify(oldInstance).teardownAsynchronously();
+
+ // Verify that even after the full timeout duration, the new VCN instance is not torn down
+ mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+ mTestLooper.dispatchAll();
+ verify(newInstance, never()).teardownAsynchronously();
+ }
+
@Test
public void testPackageChangeListenerRegistered() throws Exception {
verify(mMockContext).registerReceiver(any(BroadcastReceiver.class), argThat(filter -> {
@@ -910,8 +998,6 @@
private void setupSubscriptionAndStartVcn(
int subId, ParcelUuid subGrp, boolean isVcnActive, boolean hasCarrierPrivileges) {
mVcnMgmtSvc.systemReady();
- mTestLooper.dispatchAll();
-
triggerSubscriptionTrackerCbAndGetSnapshot(
subGrp,
Collections.singleton(subGrp),
@@ -1007,7 +1093,6 @@
private void setupTrackedCarrierWifiNetwork(NetworkCapabilities caps) {
mVcnMgmtSvc.systemReady();
- mTestLooper.dispatchAll();
final ArgumentCaptor<NetworkCallback> captor =
ArgumentCaptor.forClass(NetworkCallback.class);
@@ -1252,14 +1337,15 @@
true /* isActive */,
true /* hasCarrierPrivileges */);
- // VCN is currently active. Lose carrier privileges for TEST_PACKAGE so the VCN goes
- // inactive.
+ // VCN is currently active. Lose carrier privileges for TEST_PACKAGE and hit teardown
+ // timeout so the VCN goes inactive.
final TelephonySubscriptionSnapshot snapshot =
triggerSubscriptionTrackerCbAndGetSnapshot(
TEST_UUID_1,
Collections.singleton(TEST_UUID_1),
Collections.singletonMap(TEST_SUBSCRIPTION_ID, TEST_UUID_1),
false /* hasCarrierPrivileges */);
+ mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
mTestLooper.dispatchAll();
// Giving TEST_PACKAGE privileges again will restart the VCN (which will indicate ACTIVE