Merge "Fix the layout flag for magnification settings" into udc-dev
diff --git a/Android.bp b/Android.bp
index effd7ce..d961599 100644
--- a/Android.bp
+++ b/Android.bp
@@ -580,7 +580,7 @@
"--hide Todo " +
"--hide Typo " +
"--hide UnavailableSymbol " +
- "--manifest $(location core/res/AndroidManifest.xml) "
+ "--manifest $(location :frameworks-base-core-AndroidManifest.xml) "
packages_to_document = [
"android",
@@ -617,7 +617,7 @@
sdk_version: "none",
system_modules: "none",
java_version: "1.8",
- arg_files: ["core/res/AndroidManifest.xml"],
+ arg_files: [":frameworks-base-core-AndroidManifest.xml"],
aidl: {
local_include_dirs: [
"media/aidl",
@@ -696,12 +696,3 @@
"ProtoLibraries.bp",
"TestProtoLibraries.bp",
]
-
-java_api_contribution {
- name: "api-stubs-docs-non-updatable-public-stubs",
- api_surface: "public",
- api_file: "core/api/current.txt",
- visibility: [
- "//build/orchestrator/apis",
- ],
-}
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 38413c2..b005591 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -36,8 +36,8 @@
args: metalava_framework_docs_args,
check_api: {
current: {
- api_file: "core/api/current.txt",
- removed_api_file: "core/api/removed.txt",
+ api_file: ":non-updatable-current.txt",
+ removed_api_file: ":non-updatable-removed.txt",
},
last_released: {
api_file: ":android-non-updatable.api.public.latest",
@@ -88,8 +88,8 @@
args: metalava_framework_docs_args + priv_apps,
check_api: {
current: {
- api_file: "core/api/system-current.txt",
- removed_api_file: "core/api/system-removed.txt",
+ api_file: ":non-updatable-system-current.txt",
+ removed_api_file: ":non-updatable-system-removed.txt",
},
last_released: {
api_file: ":android-non-updatable.api.system.latest",
@@ -99,7 +99,7 @@
api_lint: {
enabled: true,
new_since: ":android.api.system.latest",
- baseline_file: "core/api/system-lint-baseline.txt",
+ baseline_file: ":non-updatable-system-lint-baseline.txt",
},
},
dists: [
@@ -127,12 +127,12 @@
args: metalava_framework_docs_args + test + priv_apps_in_stubs,
check_api: {
current: {
- api_file: "core/api/test-current.txt",
- removed_api_file: "core/api/test-removed.txt",
+ api_file: ":non-updatable-test-current.txt",
+ removed_api_file: ":non-updatable-test-removed.txt",
},
api_lint: {
enabled: true,
- baseline_file: "core/api/test-lint-baseline.txt",
+ baseline_file: ":non-updatable-test-lint-baseline.txt",
},
},
dists: [
@@ -172,8 +172,8 @@
args: metalava_framework_docs_args + priv_apps_in_stubs + module_libs,
check_api: {
current: {
- api_file: "core/api/module-lib-current.txt",
- removed_api_file: "core/api/module-lib-removed.txt",
+ api_file: ":non-updatable-module-lib-current.txt",
+ removed_api_file: ":non-updatable-module-lib-removed.txt",
},
last_released: {
api_file: ":android-non-updatable.api.module-lib.latest",
@@ -183,7 +183,7 @@
api_lint: {
enabled: true,
new_since: ":android.api.module-lib.latest",
- baseline_file: "core/api/module-lib-lint-baseline.txt",
+ baseline_file: ":non-updatable-module-lib-lint-baseline.txt",
},
},
dists: [
@@ -364,15 +364,15 @@
java_library {
name: "android_system_server_stubs_current",
- defaults: ["android_stubs_dists_default"],
+ defaults: [
+ "android.jar_defaults",
+ "android_stubs_dists_default",
+ ],
srcs: [":services-non-updatable-stubs"],
installable: false,
static_libs: [
"android_module_lib_stubs_current",
],
- sdk_version: "none",
- system_modules: "none",
- java_version: "1.8",
dist: {
dir: "apistubs/android/system-server",
},
@@ -575,20 +575,7 @@
droidstubs {
name: "hwbinder-stubs-docs",
- srcs: [
- "core/java/android/os/HidlSupport.java",
- "core/java/android/os/HidlMemory.java",
- "core/java/android/os/HwBinder.java",
- "core/java/android/os/HwBlob.java",
- "core/java/android/os/HwParcel.java",
- "core/java/android/os/IHwBinder.java",
- "core/java/android/os/IHwInterface.java",
- "core/java/android/os/DeadObjectException.java",
- "core/java/android/os/DeadSystemException.java",
- "core/java/android/os/NativeHandle.java",
- "core/java/android/os/RemoteException.java",
- "core/java/android/util/AndroidException.java",
- ],
+ srcs: [":hwbinder-stubs-srcs"],
libs: ["framework-annotations-lib"],
installable: false,
sdk_version: "core_platform",
@@ -610,12 +597,3 @@
],
visibility: ["//visibility:public"],
}
-
-java_api_contribution {
- name: "frameworks-base-core-api-module-lib-stubs",
- api_surface: "module-lib",
- api_file: "core/api/module-lib-current.txt",
- visibility: [
- "//build/orchestrator/apis",
- ],
-}
diff --git a/apct-tests/perftests/inputmethod/src/android/inputmethod/ImePerfTest.java b/apct-tests/perftests/inputmethod/src/android/inputmethod/ImePerfTest.java
index 2e44d82..e9c6c1a 100644
--- a/apct-tests/perftests/inputmethod/src/android/inputmethod/ImePerfTest.java
+++ b/apct-tests/perftests/inputmethod/src/android/inputmethod/ImePerfTest.java
@@ -76,6 +76,7 @@
implements ManualBenchmarkState.CustomizedIterationListener {
private static final String TAG = ImePerfTest.class.getSimpleName();
private static final long ANIMATION_NOT_STARTED = -1;
+ private static final int WAIT_PROCESS_KILL_TIMEOUT_MS = 2000;
@Rule
public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
@@ -248,19 +249,18 @@
boolean shouldRetry = false;
while (shouldRetry || state.keepRunning(measuredTimeNs)) {
shouldRetry = false;
- killBaselineIme();
+ killBaselineImeSync();
try (ImeSession imeSession = new ImeSession(BaselineIme.getName(
getInstrumentation().getContext()))) {
+ if (!mIsTraceStarted) {
+ startAsyncAtrace();
+ }
final AtomicReference<CountDownLatch> latchStart = new AtomicReference<>();
final Activity activity = getActivityWithFocus();
setImeListener(activity, latchStart, null /* latchEnd */);
latchStart.set(new CountDownLatch(1));
- if (!mIsTraceStarted) {
- startAsyncAtrace();
- }
-
final WindowInsetsController controller =
activity.getWindow().getDecorView().getWindowInsetsController();
AtomicLong startTime = new AtomicLong();
@@ -270,6 +270,7 @@
});
measuredTimeNs = waitForAnimationStart(latchStart, startTime);
+ stopAsyncAtraceAndDumpTraces();
if (measuredTimeNs == ANIMATION_NOT_STARTED) {
// Animation didn't start within timeout,
@@ -285,7 +286,7 @@
addResultToState(state);
}
- private void killBaselineIme() {
+ private void killBaselineImeSync() {
// pidof returns a space separated list of numeric PIDs.
String result = SystemUtil.runShellCommand(
"pidof com.android.perftests.inputmethod:BaselineIME");
@@ -294,7 +295,13 @@
if (TextUtils.isEmpty(pid)) {
continue;
}
- Process.killProcess(Integer.parseInt(pid));
+ final int pidToKill = Integer.parseInt(pid);
+ Process.killProcess(pidToKill);
+ try {
+ // Wait kill IME process being settled down.
+ Process.waitForProcessDeath(pidToKill, WAIT_PROCESS_KILL_TIMEOUT_MS);
+ } catch (Exception e) {
+ }
}
}
@@ -381,7 +388,7 @@
}
} finally {
if (mIsTraceStarted) {
- stopAsyncAtrace();
+ stopAsyncAtraceAndDumpTraces();
}
}
mActivityRule.finishActivity();
@@ -488,7 +495,7 @@
startAsyncAtrace("wm view");
}
- private void stopAsyncAtrace() {
+ private void stopAsyncAtraceAndDumpTraces() {
if (!mIsTraceStarted) {
return;
}
@@ -504,6 +511,14 @@
}
}
+ private void stopAsyncAtrace() {
+ if (!mIsTraceStarted) {
+ return;
+ }
+ mIsTraceStarted = false;
+ getUiAutomation().executeShellCommand("atrace --async_stop");
+ }
+
@Override
public void onStart(int iteration) {
// Do not capture trace when profiling because the result will be much slower.
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfTestBase.java b/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfTestBase.java
index ca59137..804baf4 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfTestBase.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfTestBase.java
@@ -73,7 +73,7 @@
}
public static void startAsyncAtrace(String tags) {
- getUiAutomation().executeShellCommand("atrace -b 32768 --async_start " + tags);
+ getUiAutomation().executeShellCommand("atrace --async_start -b 32768 -c " + tags);
// Avoid atrace isn't ready immediately.
SystemClock.sleep(TimeUnit.NANOSECONDS.toMillis(TIME_1_S_IN_NS));
}
diff --git a/apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java b/apex/jobscheduler/service/java/com/android/server/AppSchedulingModuleThread.java
similarity index 69%
rename from apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java
rename to apex/jobscheduler/service/java/com/android/server/AppSchedulingModuleThread.java
index a413f7b..d36f44f 100644
--- a/apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppSchedulingModuleThread.java
@@ -20,29 +20,30 @@
import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.Process;
import android.os.Trace;
import java.util.concurrent.Executor;
/**
- * Shared singleton background thread.
+ * Shared singleton default priority thread for the app scheduling module.
*
* @see com.android.internal.os.BackgroundThread
*/
-public final class JobSchedulerBackgroundThread extends HandlerThread {
+public final class AppSchedulingModuleThread extends HandlerThread {
private static final long SLOW_DISPATCH_THRESHOLD_MS = 10_000;
private static final long SLOW_DELIVERY_THRESHOLD_MS = 30_000;
- private static JobSchedulerBackgroundThread sInstance;
+ private static AppSchedulingModuleThread sInstance;
private static Handler sHandler;
private static Executor sHandlerExecutor;
- private JobSchedulerBackgroundThread() {
- super("jobscheduler.bg", android.os.Process.THREAD_PRIORITY_BACKGROUND);
+ private AppSchedulingModuleThread() {
+ super("appscheduling.default", Process.THREAD_PRIORITY_DEFAULT);
}
private static void ensureThreadLocked() {
if (sInstance == null) {
- sInstance = new JobSchedulerBackgroundThread();
+ sInstance = new AppSchedulingModuleThread();
sInstance.start();
final Looper looper = sInstance.getLooper();
looper.setTraceTag(Trace.TRACE_TAG_SYSTEM_SERVER);
@@ -53,25 +54,25 @@
}
}
- /** Returns the JobSchedulerBackgroundThread singleton */
- public static JobSchedulerBackgroundThread get() {
- synchronized (JobSchedulerBackgroundThread.class) {
+ /** Returns the AppSchedulingModuleThread singleton */
+ public static AppSchedulingModuleThread get() {
+ synchronized (AppSchedulingModuleThread.class) {
ensureThreadLocked();
return sInstance;
}
}
- /** Returns the singleton handler for JobSchedulerBackgroundThread */
+ /** Returns the singleton handler for AppSchedulingModuleThread */
public static Handler getHandler() {
- synchronized (JobSchedulerBackgroundThread.class) {
+ synchronized (AppSchedulingModuleThread.class) {
ensureThreadLocked();
return sHandler;
}
}
- /** Returns the singleton handler executor for JobSchedulerBackgroundThread */
+ /** Returns the singleton handler executor for AppSchedulingModuleThread */
public static Executor getExecutor() {
- synchronized (JobSchedulerBackgroundThread.class) {
+ synchronized (AppSchedulingModuleThread.class) {
ensureThreadLocked();
return sHandlerExecutor;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 5de1172..9e4321d 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -1327,7 +1327,7 @@
IDLE_AFTER_INACTIVE_TIMEOUT = DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT_SMALL_BATTERY;
}
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DEVICE_IDLE,
- JobSchedulerBackgroundThread.getExecutor(), this);
+ AppSchedulingModuleThread.getExecutor(), this);
// Load all the constants.
onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_DEVICE_IDLE));
}
@@ -2416,7 +2416,7 @@
}
MyHandler getHandler(DeviceIdleController controller) {
- return controller.new MyHandler(JobSchedulerBackgroundThread.getHandler().getLooper());
+ return controller.new MyHandler(AppSchedulingModuleThread.getHandler().getLooper());
}
Sensor getMotionSensor() {
@@ -2488,7 +2488,7 @@
mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
mHandler = mInjector.getHandler(this);
mAppStateTracker = mInjector.getAppStateTracker(context,
- JobSchedulerBackgroundThread.get().getLooper());
+ AppSchedulingModuleThread.get().getLooper());
LocalServices.addService(AppStateTracker.class, mAppStateTracker);
mUseMotionSensor = mInjector.useMotionSensor();
}
@@ -2681,7 +2681,7 @@
mLocalActivityTaskManager.registerScreenObserver(mScreenObserver);
mInjector.getTelephonyManager().registerTelephonyCallback(
- JobSchedulerBackgroundThread.getExecutor(), mEmergencyCallListener);
+ AppSchedulingModuleThread.getExecutor(), mEmergencyCallListener);
passWhiteListsToForceAppStandbyTrackerLocked();
updateInteractivityLocked();
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index deb8a63..1151bb7 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -147,12 +147,12 @@
import com.android.internal.util.RingBuffer;
import com.android.internal.util.StatLogger;
import com.android.server.AlarmManagerInternal;
+import com.android.server.AppSchedulingModuleThread;
import com.android.server.AppStateTracker;
import com.android.server.AppStateTrackerImpl;
import com.android.server.AppStateTrackerImpl.Listener;
import com.android.server.DeviceIdleInternal;
import com.android.server.EventLogTags;
-import com.android.server.JobSchedulerBackgroundThread;
import com.android.server.LocalServices;
import com.android.server.SystemClockTime;
import com.android.server.SystemClockTime.TimeConfidence;
@@ -4691,7 +4691,7 @@
void registerDeviceConfigListener(DeviceConfig.OnPropertiesChangedListener listener) {
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ALARM_MANAGER,
- JobSchedulerBackgroundThread.getExecutor(), listener);
+ AppSchedulingModuleThread.getExecutor(), listener);
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 272a79e..89eb1a9 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -62,7 +62,7 @@
import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.util.MemInfoReader;
import com.android.internal.util.StatLogger;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
import com.android.server.LocalServices;
import com.android.server.job.controllers.JobStatus;
import com.android.server.job.controllers.StateController;
@@ -499,7 +499,7 @@
mInjector = injector;
mNotificationCoordinator = new JobNotificationCoordinator();
- mHandler = JobSchedulerBackgroundThread.getHandler();
+ mHandler = AppSchedulingModuleThread.getHandler();
mGracePeriodObserver = new GracePeriodObserver(mContext);
mShouldRestrictBgUser = mContext.getResources().getBoolean(
@@ -533,7 +533,8 @@
mIdleContexts.add(
mInjector.createJobServiceContext(mService, this,
mNotificationCoordinator, batteryStats,
- mService.mJobPackageTracker, mContext.getMainLooper()));
+ mService.mJobPackageTracker,
+ AppSchedulingModuleThread.get().getLooper()));
}
}
@@ -1925,7 +1926,7 @@
return mInjector.createJobServiceContext(mService, this, mNotificationCoordinator,
IBatteryStats.Stub.asInterface(
ServiceManager.getService(BatteryStats.SERVICE_NAME)),
- mService.mJobPackageTracker, mContext.getMainLooper());
+ mService.mJobPackageTracker, AppSchedulingModuleThread.get().getLooper());
}
@GuardedBy("mLock")
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 0af191a..3cc67e7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -105,10 +105,10 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.AppSchedulingModuleThread;
import com.android.server.AppStateTracker;
import com.android.server.AppStateTrackerImpl;
import com.android.server.DeviceIdleInternal;
-import com.android.server.JobSchedulerBackgroundThread;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
import com.android.server.job.controllers.BackgroundJobsController;
@@ -411,7 +411,7 @@
EconomyManagerInternal.TareStateChangeListener {
public void start() {
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
- JobSchedulerBackgroundThread.getExecutor(), this);
+ AppSchedulingModuleThread.getExecutor(), this);
final EconomyManagerInternal economyManagerInternal =
LocalServices.getService(EconomyManagerInternal.class);
economyManagerInternal
@@ -2009,7 +2009,7 @@
mActivityManagerInternal = Objects.requireNonNull(
LocalServices.getService(ActivityManagerInternal.class));
- mHandler = new JobHandler(context.getMainLooper());
+ mHandler = new JobHandler(AppSchedulingModuleThread.get().getLooper());
mConstants = new Constants();
mConstantsObserver = new ConstantsObserver();
mJobSchedulerStub = new JobSchedulerStub();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
index 2ca3f8f..4c55dac 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
@@ -35,7 +35,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;
@@ -151,7 +151,7 @@
@GuardedBy("mLock")
public void onBatteryStateChangedLocked() {
// Update job bookkeeping out of band.
- JobSchedulerBackgroundThread.getHandler().post(() -> {
+ AppSchedulingModuleThread.getHandler().post(() -> {
synchronized (mLock) {
maybeReportNewChargingStateLocked();
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 2a9186a..e55bda7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -52,7 +52,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobSchedulerService.Constants;
@@ -256,7 +256,7 @@
public ConnectivityController(JobSchedulerService service,
@NonNull FlexibilityController flexibilityController) {
super(service);
- mHandler = new CcHandler(mContext.getMainLooper());
+ mHandler = new CcHandler(AppSchedulingModuleThread.get().getLooper());
mConnManager = mContext.getSystemService(ConnectivityManager.class);
mNetPolicyManagerInternal = LocalServices.getService(NetworkPolicyManagerInternal.class);
@@ -1347,7 +1347,7 @@
TelephonyManager idTm = telephonyManager.createForSubscriptionId(subId);
CellSignalStrengthCallback callback = new CellSignalStrengthCallback();
idTm.registerTelephonyCallback(
- JobSchedulerBackgroundThread.getExecutor(), callback);
+ AppSchedulingModuleThread.getExecutor(), callback);
mSignalStrengths.put(subId, callback);
final SignalStrength signalStrength = idTm.getSignalStrength();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
index 847a1bf..122fe69 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
@@ -33,6 +33,7 @@
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
+import com.android.server.AppSchedulingModuleThread;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;
import com.android.server.job.StateControllerProto.ContentObserverController.Observer.TriggerContentData;
@@ -70,7 +71,7 @@
public ContentObserverController(JobSchedulerService service) {
super(service);
- mHandler = new Handler(mContext.getMainLooper());
+ mHandler = new Handler(AppSchedulingModuleThread.get().getLooper());
}
@Override
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index bdf72b6..d5c9ae6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -36,6 +36,7 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.ArrayUtils;
+import com.android.server.AppSchedulingModuleThread;
import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
@@ -127,7 +128,7 @@
public DeviceIdleJobsController(JobSchedulerService service) {
super(service);
- mHandler = new DeviceIdleJobsDelayHandler(mContext.getMainLooper());
+ mHandler = new DeviceIdleJobsDelayHandler(AppSchedulingModuleThread.get().getLooper());
// Register for device idle mode changes
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mLocalDeviceIdleController =
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 4c17692..620c48d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -44,7 +44,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
import com.android.server.job.JobSchedulerService;
import com.android.server.utils.AlarmQueue;
@@ -183,7 +183,7 @@
mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS);
mFcConfig = new FcConfig();
mFlexibilityAlarmQueue = new FlexibilityAlarmQueue(
- mContext, JobSchedulerBackgroundThread.get().getLooper());
+ mContext, AppSchedulingModuleThread.get().getLooper());
mPercentToDropConstraints =
mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
mPrefetchController = prefetchController;
@@ -427,7 +427,7 @@
@GuardedBy("mLock")
public void onConstantsUpdatedLocked() {
if (mFcConfig.mShouldReevaluateConstraints) {
- JobSchedulerBackgroundThread.getHandler().post(() -> {
+ AppSchedulingModuleThread.getHandler().post(() -> {
final ArraySet<JobStatus> changedJobs = new ArraySet<>();
synchronized (mLock) {
final long nowElapsed = sElapsedRealtimeClock.millis();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
index c46ffd7..2b7438c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
@@ -46,7 +46,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.utils.AlarmQueue;
@@ -130,9 +130,9 @@
public PrefetchController(JobSchedulerService service) {
super(service);
mPcConstants = new PcConstants();
- mHandler = new PcHandler(mContext.getMainLooper());
+ mHandler = new PcHandler(AppSchedulingModuleThread.get().getLooper());
mThresholdAlarmListener = new ThresholdAlarmListener(
- mContext, JobSchedulerBackgroundThread.get().getLooper());
+ mContext, AppSchedulingModuleThread.get().getLooper());
mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class);
mUsageStatsManagerInternal
@@ -400,7 +400,7 @@
public void onConstantsUpdatedLocked() {
if (mPcConstants.mShouldReevaluateConstraints) {
// Update job bookkeeping out of band.
- JobSchedulerBackgroundThread.getHandler().post(() -> {
+ AppSchedulingModuleThread.getHandler().post(() -> {
final ArraySet<JobStatus> changedJobs = new ArraySet<>();
synchronized (mLock) {
final long nowElapsed = sElapsedRealtimeClock.millis();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index cbfad94..aca0a6e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -65,7 +65,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
import com.android.server.LocalServices;
import com.android.server.PowerAllowlistInternal;
import com.android.server.job.ConstantsProto;
@@ -560,13 +560,14 @@
@NonNull BackgroundJobsController backgroundJobsController,
@NonNull ConnectivityController connectivityController) {
super(service);
- mHandler = new QcHandler(mContext.getMainLooper());
+ mHandler = new QcHandler(AppSchedulingModuleThread.get().getLooper());
mAlarmManager = mContext.getSystemService(AlarmManager.class);
mQcConstants = new QcConstants();
mBackgroundJobsController = backgroundJobsController;
mConnectivityController = connectivityController;
mIsEnabled = !mConstants.USE_TARE_POLICY;
- mInQuotaAlarmQueue = new InQuotaAlarmQueue(mContext, mContext.getMainLooper());
+ mInQuotaAlarmQueue =
+ new InQuotaAlarmQueue(mContext, AppSchedulingModuleThread.get().getLooper());
// Set up the app standby bucketing tracker
AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class);
@@ -1649,7 +1650,7 @@
mEJPkgTimers.forEach(mTimerChargingUpdateFunctor);
mPkgTimers.forEach(mTimerChargingUpdateFunctor);
// Now update jobs out of band so broadcast processing can proceed.
- JobSchedulerBackgroundThread.getHandler().post(() -> {
+ AppSchedulingModuleThread.getHandler().post(() -> {
synchronized (mLock) {
maybeUpdateAllConstraintsLocked();
}
@@ -2486,7 +2487,7 @@
public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId,
boolean idle, int bucket, int reason) {
// Update job bookkeeping out of band.
- JobSchedulerBackgroundThread.getHandler().post(() -> {
+ AppSchedulingModuleThread.getHandler().post(() -> {
final int bucketIndex = JobSchedulerService.standbyBucketToBucketIndex(bucket);
updateStandbyBucket(userId, packageName, bucketIndex);
});
@@ -2938,7 +2939,7 @@
if (mQcConstants.mShouldReevaluateConstraints || mIsEnabled == mConstants.USE_TARE_POLICY) {
mIsEnabled = !mConstants.USE_TARE_POLICY;
// Update job bookkeeping out of band.
- JobSchedulerBackgroundThread.getHandler().post(() -> {
+ AppSchedulingModuleThread.getHandler().post(() -> {
synchronized (mLock) {
invalidateAllExecutionStatsLocked();
maybeUpdateAllConstraintsLocked();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java
index 792155b..7408088 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java
@@ -30,7 +30,7 @@
import android.util.SparseArrayMap;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.tare.EconomicPolicy;
@@ -424,7 +424,7 @@
if (mIsEnabled != mConstants.USE_TARE_POLICY) {
mIsEnabled = mConstants.USE_TARE_POLICY;
// Update job bookkeeping out of band.
- JobSchedulerBackgroundThread.getHandler().post(() -> {
+ AppSchedulingModuleThread.getHandler().post(() -> {
synchronized (mLock) {
final long nowElapsed = sElapsedRealtimeClock.millis();
mService.getJobStore().forEachJob((jobStatus) -> {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java
index 47b7e13..c458cae 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java
@@ -24,7 +24,7 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
import com.android.server.am.ActivityManagerService;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;
@@ -91,7 +91,7 @@
filter.addAction(ACTION_UNFORCE_IDLE);
filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE);
- context.registerReceiver(this, filter, null, JobSchedulerBackgroundThread.getHandler());
+ context.registerReceiver(this, filter, null, AppSchedulingModuleThread.getHandler());
}
@Override
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
index 15d6766..c943e73 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
@@ -31,7 +31,7 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
import com.android.server.am.ActivityManagerService;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;
@@ -102,10 +102,10 @@
filter.addAction(Intent.ACTION_DOCK_IDLE);
filter.addAction(Intent.ACTION_DOCK_ACTIVE);
- context.registerReceiver(this, filter, null, JobSchedulerBackgroundThread.getHandler());
+ context.registerReceiver(this, filter, null, AppSchedulingModuleThread.getHandler());
context.getSystemService(UiModeManager.class).addOnProjectionStateChangedListener(
- UiModeManager.PROJECTION_TYPE_ALL, JobSchedulerBackgroundThread.getExecutor(),
+ UiModeManager.PROJECTION_TYPE_ALL, AppSchedulingModuleThread.getExecutor(),
mOnProjectionStateChangedListener);
}
@@ -226,7 +226,7 @@
}
mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
when, mIdleWindowSlop, "JS idleness",
- JobSchedulerBackgroundThread.getExecutor(), mIdleAlarmListener);
+ AppSchedulingModuleThread.getExecutor(), mIdleAlarmListener);
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index a3d566b..c3118ff 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -125,7 +125,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ConcurrentUtils;
import com.android.server.AlarmManagerInternal;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
import com.android.server.LocalServices;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.usage.AppIdleHistory.AppUsageHistory;
@@ -592,7 +592,7 @@
}
public AppStandbyController(Context context) {
- this(new Injector(context, JobSchedulerBackgroundThread.get().getLooper()));
+ this(new Injector(context, AppSchedulingModuleThread.get().getLooper()));
}
AppStandbyController(Injector injector) {
@@ -2761,7 +2761,7 @@
void registerDeviceConfigPropertiesChangedListener(
@NonNull DeviceConfig.OnPropertiesChangedListener listener) {
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APP_STANDBY,
- JobSchedulerBackgroundThread.getExecutor(), listener);
+ AppSchedulingModuleThread.getExecutor(), listener);
}
void dump(PrintWriter pw) {
diff --git a/api/Android.bp b/api/Android.bp
index 78ddc6a..73dbd28 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -31,10 +31,12 @@
"blueprint",
"soong",
"soong-android",
+ "soong-bp2build",
"soong-genrule",
"soong-java",
],
srcs: ["api.go"],
+ testSrcs: ["api_test.go"],
pluginFor: ["soong_build"],
}
@@ -261,3 +263,15 @@
out: ["combined-removed-dex.txt"],
cmd: "$(location gen_combined_removed_dex.sh) $(location metalava) $(genDir) $(in) > $(out)",
}
+
+java_genrule {
+ name: "api_fingerprint",
+ srcs: [
+ ":frameworks-base-api-current.txt",
+ ":frameworks-base-api-system-current.txt",
+ ":frameworks-base-api-module-lib-current.txt",
+ ":frameworks-base-api-system-server-current.txt",
+ ],
+ out: ["api_fingerprint.txt"],
+ cmd: "cat $(in) | md5sum | cut -d' ' -f1 > $(out)",
+}
diff --git a/api/api.go b/api/api.go
index 077ab96..25d9728 100644
--- a/api/api.go
+++ b/api/api.go
@@ -20,6 +20,7 @@
"github.com/google/blueprint/proptools"
"android/soong/android"
+ "android/soong/bazel"
"android/soong/genrule"
"android/soong/java"
)
@@ -30,6 +31,7 @@
const virtualization = "framework-virtualization"
var core_libraries_modules = []string{art, conscrypt, i18n}
+
// List of modules that are not yet updatable, and hence they can still compile
// against hidden APIs. These modules are filtered out when building the
// updatable-framework-module-impl (because updatable-framework-module-impl is
@@ -59,6 +61,7 @@
type CombinedApis struct {
android.ModuleBase
+ android.BazelModuleBase
properties CombinedApisProperties
}
@@ -99,6 +102,19 @@
Visibility []string
}
+type Bazel_module struct {
+ Bp2build_available *bool
+}
+type bazelProperties struct {
+ *Bazel_module
+}
+
+var bp2buildNotAvailable = bazelProperties{
+ &Bazel_module{
+ Bp2build_available: proptools.BoolPtr(false),
+ },
+}
+
// Struct to pass parameters for the various merged [current|removed].txt file modules we create.
type MergedTxtDefinition struct {
// "current.txt" or "removed.txt"
@@ -144,7 +160,7 @@
},
}
props.Visibility = []string{"//visibility:public"}
- ctx.CreateModule(genrule.GenRuleFactory, &props)
+ ctx.CreateModule(genrule.GenRuleFactory, &props, &bp2buildNotAvailable)
}
func createMergedAnnotationsFilegroups(ctx android.LoadHookContext, modules, system_server_modules []string) {
@@ -174,7 +190,7 @@
props := fgProps{}
props.Name = proptools.StringPtr(i.name)
props.Srcs = createSrcs(i.modules, i.tag)
- ctx.CreateModule(android.FileGroupFactory, &props)
+ ctx.CreateModule(android.FileGroupFactory, &props, &bp2buildNotAvailable)
}
}
@@ -223,7 +239,7 @@
props.Tools = []string{"api_versions_trimmer"}
props.Cmd = proptools.StringPtr("$(location api_versions_trimmer) $(out) $(in)")
props.Dists = []android.Dist{{Targets: []string{"sdk"}}}
- ctx.CreateModule(genrule.GenRuleFactory, &props)
+ ctx.CreateModule(genrule.GenRuleFactory, &props, &bp2buildNotAvailable)
}
}
@@ -315,7 +331,7 @@
props.Name = proptools.StringPtr("all-modules-public-stubs-source")
props.Srcs = createSrcs(modules, "{.public.stubs.source}")
props.Visibility = []string{"//frameworks/base"}
- ctx.CreateModule(android.FileGroupFactory, &props)
+ ctx.CreateModule(android.FileGroupFactory, &props, &bp2buildNotAvailable)
}
func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) {
@@ -389,9 +405,57 @@
module.AddProperties(&module.properties)
android.InitAndroidModule(module)
android.AddLoadHook(module, func(ctx android.LoadHookContext) { module.createInternalModules(ctx) })
+ android.InitBazelModule(module)
return module
}
+type bazelCombinedApisAttributes struct {
+ Scope bazel.StringAttribute
+ Base bazel.LabelAttribute
+ Deps bazel.LabelListAttribute
+}
+
+// combined_apis bp2build converter
+func (a *CombinedApis) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+ basePrefix := "non-updatable"
+ scopeNames := []string{"public", "system", "module-lib", "system-server"}
+ scopeToSuffix := map[string]string{
+ "public": "-current.txt",
+ "system": "-system-current.txt",
+ "module-lib": "-module-lib-current.txt",
+ "system-server": "-system-server-current.txt",
+ }
+
+ for _, scopeName := range scopeNames{
+ suffix := scopeToSuffix[scopeName]
+ name := a.Name() + suffix
+
+ var scope bazel.StringAttribute
+ scope.SetValue(scopeName)
+
+ var base bazel.LabelAttribute
+ base.SetValue(android.BazelLabelForModuleDepSingle(ctx, basePrefix+suffix))
+
+ var deps bazel.LabelListAttribute
+ classpath := a.properties.Bootclasspath
+ if scopeName == "system-server" {
+ classpath = a.properties.System_server_classpath
+ }
+ deps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, classpath))
+
+ attrs := bazelCombinedApisAttributes{
+ Scope: scope,
+ Base: base,
+ Deps: deps,
+ }
+ props := bazel.BazelTargetModuleProperties{
+ Rule_class: "merged_txts",
+ Bzl_load_location: "//build/bazel/rules/java:merged_txts.bzl",
+ }
+ ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: name}, &attrs)
+ }
+}
+
// Various utility methods below.
// Creates an array of ":<m><tag>" for each m in <modules>.
diff --git a/api/api_test.go b/api/api_test.go
new file mode 100644
index 0000000..15b695c
--- /dev/null
+++ b/api/api_test.go
@@ -0,0 +1,68 @@
+// Copyright (C) 2023 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 api
+
+import (
+ "testing"
+
+ "android/soong/android"
+ "android/soong/bp2build"
+)
+
+func runCombinedApisTestCaseWithRegistrationCtxFunc(t *testing.T, tc bp2build.Bp2buildTestCase, registrationCtxFunc func(ctx android.RegistrationContext)) {
+ t.Helper()
+ (&tc).ModuleTypeUnderTest = "combined_apis"
+ (&tc).ModuleTypeUnderTestFactory = combinedApisModuleFactory
+ bp2build.RunBp2BuildTestCase(t, registrationCtxFunc, tc)
+}
+
+func runCombinedApisTestCase(t *testing.T, tc bp2build.Bp2buildTestCase) {
+ t.Helper()
+ runCombinedApisTestCaseWithRegistrationCtxFunc(t, tc, func(ctx android.RegistrationContext) {})
+}
+
+func TestCombinedApisGeneral(t *testing.T) {
+ runCombinedApisTestCase(t, bp2build.Bp2buildTestCase{
+ Description: "combined_apis, general case",
+ Blueprint: `combined_apis {
+ name: "foo",
+ bootclasspath: ["bcp"],
+ system_server_classpath: ["ssc"],
+}
+`,
+ ExpectedBazelTargets: []string{
+ bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-current.txt", bp2build.AttrNameToString{
+ "scope": `"public"`,
+ "base": `":non-updatable-current.txt__BP2BUILD__MISSING__DEP"`,
+ "deps": `[":bcp__BP2BUILD__MISSING__DEP"]`,
+ }),
+ bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-system-current.txt", bp2build.AttrNameToString{
+ "scope": `"system"`,
+ "base": `":non-updatable-system-current.txt__BP2BUILD__MISSING__DEP"`,
+ "deps": `[":bcp__BP2BUILD__MISSING__DEP"]`,
+ }),
+ bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-module-lib-current.txt", bp2build.AttrNameToString{
+ "scope": `"module-lib"`,
+ "base": `":non-updatable-module-lib-current.txt__BP2BUILD__MISSING__DEP"`,
+ "deps": `[":bcp__BP2BUILD__MISSING__DEP"]`,
+ }),
+ bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-system-server-current.txt", bp2build.AttrNameToString{
+ "scope": `"system-server"`,
+ "base": `":non-updatable-system-server-current.txt__BP2BUILD__MISSING__DEP"`,
+ "deps": `[":ssc__BP2BUILD__MISSING__DEP"]`,
+ }),
+ },
+ })
+}
diff --git a/core/api/Android.bp b/core/api/Android.bp
index 114a957..71a2ca2 100644
--- a/core/api/Android.bp
+++ b/core/api/Android.bp
@@ -13,7 +13,10 @@
// limitations under the License.
package {
- default_visibility: ["//visibility:private"],
+ default_visibility: [
+ "//frameworks/base",
+ "//frameworks/base/api",
+ ],
// 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"
@@ -27,31 +30,33 @@
filegroup {
name: "non-updatable-current.txt",
srcs: ["current.txt"],
- visibility: ["//frameworks/base/api"],
}
filegroup {
name: "non-updatable-removed.txt",
srcs: ["removed.txt"],
- visibility: ["//frameworks/base/api"],
}
filegroup {
name: "non-updatable-system-current.txt",
srcs: ["system-current.txt"],
- visibility: ["//frameworks/base/api"],
}
filegroup {
name: "non-updatable-system-removed.txt",
srcs: ["system-removed.txt"],
- visibility: ["//frameworks/base/api"],
+}
+
+filegroup {
+ name: "non-updatable-system-lint-baseline.txt",
+ srcs: ["system-lint-baseline.txt"],
}
filegroup {
name: "non-updatable-module-lib-current.txt",
srcs: ["module-lib-current.txt"],
visibility: [
+ "//frameworks/base",
"//frameworks/base/api",
"//cts/tests/signature/api",
],
@@ -61,7 +66,46 @@
name: "non-updatable-module-lib-removed.txt",
srcs: ["module-lib-removed.txt"],
visibility: [
+ "//frameworks/base",
"//frameworks/base/api",
"//cts/tests/signature/api",
],
}
+
+filegroup {
+ name: "non-updatable-module-lib-lint-baseline.txt",
+ srcs: ["module-lib-lint-baseline.txt"],
+}
+
+filegroup {
+ name: "non-updatable-test-current.txt",
+ srcs: ["test-current.txt"],
+}
+
+filegroup {
+ name: "non-updatable-test-removed.txt",
+ srcs: ["test-removed.txt"],
+}
+
+filegroup {
+ name: "non-updatable-test-lint-baseline.txt",
+ srcs: ["test-lint-baseline.txt"],
+}
+
+java_api_contribution {
+ name: "api-stubs-docs-non-updatable-public-stubs",
+ api_surface: "public",
+ api_file: "current.txt",
+ visibility: [
+ "//build/orchestrator/apis",
+ ],
+}
+
+java_api_contribution {
+ name: "frameworks-base-core-api-module-lib-stubs",
+ api_surface: "module-lib",
+ api_file: "module-lib-current.txt",
+ visibility: [
+ "//build/orchestrator/apis",
+ ],
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index be36d63..5b9970b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5279,10 +5279,28 @@
}
public class BroadcastOptions {
- ctor public BroadcastOptions();
+ method public void clearDeferralPolicy();
+ method public void clearDeliveryGroupMatchingFilter();
+ method public void clearDeliveryGroupMatchingKey();
+ method public void clearDeliveryGroupPolicy();
+ method @NonNull public static android.app.BroadcastOptions fromBundle(@NonNull android.os.Bundle);
+ method public int getDeferralPolicy();
+ method @Nullable public android.content.IntentFilter getDeliveryGroupMatchingFilter();
+ method @Nullable public String getDeliveryGroupMatchingKey();
+ method public int getDeliveryGroupPolicy();
method public boolean isShareIdentityEnabled();
+ method @NonNull public static android.app.BroadcastOptions makeBasic();
+ method @NonNull public android.app.BroadcastOptions setDeferralPolicy(int);
+ method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingFilter(@NonNull android.content.IntentFilter);
+ method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String, @NonNull String);
+ method @NonNull public android.app.BroadcastOptions setDeliveryGroupPolicy(int);
method @NonNull public android.app.BroadcastOptions setShareIdentityEnabled(boolean);
method @NonNull public android.os.Bundle toBundle();
+ field public static final int DEFERRAL_POLICY_DEFAULT = 0; // 0x0
+ field public static final int DEFERRAL_POLICY_NONE = 1; // 0x1
+ field public static final int DEFERRAL_POLICY_UNTIL_ACTIVE = 2; // 0x2
+ field public static final int DELIVERY_GROUP_POLICY_ALL = 0; // 0x0
+ field public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1; // 0x1
}
public class DatePickerDialog extends android.app.AlertDialog implements android.widget.DatePicker.OnDateChangedListener android.content.DialogInterface.OnClickListener {
@@ -7398,12 +7416,15 @@
}
public class UiModeManager {
+ method public void addContrastChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.UiModeManager.ContrastChangeListener);
method public void disableCarMode(int);
method public void enableCarMode(int);
+ method @FloatRange(from=-1.0F, to=1.0f) public float getContrast();
method public int getCurrentModeType();
method @NonNull public java.time.LocalTime getCustomNightModeEnd();
method @NonNull public java.time.LocalTime getCustomNightModeStart();
method public int getNightMode();
+ method public void removeContrastChangeListener(@NonNull android.app.UiModeManager.ContrastChangeListener);
method public void setApplicationNightMode(int);
method public void setCustomNightModeEnd(@NonNull java.time.LocalTime);
method public void setCustomNightModeStart(@NonNull java.time.LocalTime);
@@ -7421,6 +7442,10 @@
field public static final int MODE_NIGHT_YES = 2; // 0x2
}
+ public static interface UiModeManager.ContrastChangeListener {
+ method public void onContrastChanged(@FloatRange(from=-1.0F, to=1.0f) float);
+ }
+
public final class VoiceInteractor {
method public android.app.VoiceInteractor.Request getActiveRequest(String);
method public android.app.VoiceInteractor.Request[] getActiveRequests();
@@ -11157,6 +11182,7 @@
method public final String getDataScheme(int);
method public final android.os.PatternMatcher getDataSchemeSpecificPart(int);
method public final String getDataType(int);
+ method @NonNull public final android.os.PersistableBundle getExtras();
method public final int getPriority();
method public final boolean hasAction(String);
method public final boolean hasCategory(String);
@@ -11175,6 +11201,7 @@
method public void readFromXml(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public final java.util.Iterator<android.os.PatternMatcher> schemeSpecificPartsIterator();
method public final java.util.Iterator<java.lang.String> schemesIterator();
+ method public final void setExtras(@NonNull android.os.PersistableBundle);
method public final void setPriority(int);
method public final java.util.Iterator<java.lang.String> typesIterator();
method public final void writeToParcel(android.os.Parcel, int);
@@ -12731,6 +12758,7 @@
field public static final String FEATURE_VULKAN_HARDWARE_COMPUTE = "android.hardware.vulkan.compute";
field public static final String FEATURE_VULKAN_HARDWARE_LEVEL = "android.hardware.vulkan.level";
field public static final String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version";
+ field public static final String FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS = "android.software.wallet_location_based_suggestions";
field public static final String FEATURE_WATCH = "android.hardware.type.watch";
field public static final String FEATURE_WEBVIEW = "android.software.webview";
field public static final String FEATURE_WIFI = "android.hardware.wifi";
@@ -15484,14 +15512,14 @@
method @NonNull public float getMinDisplayRatioForHdrTransition();
method @NonNull public float[] getRatioMax();
method @NonNull public float[] getRatioMin();
- method @NonNull public void setDisplayRatioForFullHdr(float);
- method @NonNull public void setEpsilonHdr(float, float, float);
- method @NonNull public void setEpsilonSdr(float, float, float);
+ method public void setDisplayRatioForFullHdr(@FloatRange(from=1.0f) float);
+ method public void setEpsilonHdr(float, float, float);
+ method public void setEpsilonSdr(float, float, float);
method public void setGainmapContents(@NonNull android.graphics.Bitmap);
- method @NonNull public void setGamma(float, float, float);
- method @NonNull public void setMinDisplayRatioForHdrTransition(@FloatRange(from=1.0f) float);
- method @NonNull public void setRatioMax(float, float, float);
- method @NonNull public void setRatioMin(float, float, float);
+ method public void setGamma(float, float, float);
+ method public void setMinDisplayRatioForHdrTransition(@FloatRange(from=1.0f) float);
+ method public void setRatioMax(float, float, float);
+ method public void setRatioMin(float, float, float);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.graphics.Gainmap> CREATOR;
}
@@ -26275,9 +26303,9 @@
public final class MediaProjection {
method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, int, @Nullable android.view.Surface, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler);
- method public void registerCallback(android.media.projection.MediaProjection.Callback, android.os.Handler);
+ method public void registerCallback(@NonNull android.media.projection.MediaProjection.Callback, @Nullable android.os.Handler);
method public void stop();
- method public void unregisterCallback(android.media.projection.MediaProjection.Callback);
+ method public void unregisterCallback(@NonNull android.media.projection.MediaProjection.Callback);
}
public abstract static class MediaProjection.Callback {
@@ -27308,6 +27336,7 @@
field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2
field public static final int TIME_SHIFT_STATUS_UNKNOWN = 0; // 0x0
field public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 1; // 0x1
+ field public static final String TV_MESSAGE_KEY_STREAM_ID = "android.media.tv.TvInputManager.stream_id";
field public static final String TV_MESSAGE_TYPE_CLOSED_CAPTION = "CC";
field public static final String TV_MESSAGE_TYPE_WATERMARK = "Watermark";
field public static final int VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY = 4; // 0x4
@@ -27436,6 +27465,7 @@
method public boolean onTrackballEvent(android.view.MotionEvent);
method public abstract boolean onTune(android.net.Uri);
method public boolean onTune(android.net.Uri, android.os.Bundle);
+ method public void onTvMessage(@NonNull String, @NonNull android.os.Bundle);
method public void onUnblockContent(android.media.tv.TvContentRating);
method public void setOverlayViewEnabled(boolean);
}
@@ -36889,6 +36919,7 @@
method public String[] getDocumentStreamTypes(String, String);
method public String getDocumentType(String) throws java.io.FileNotFoundException;
method public final String getType(android.net.Uri);
+ method @Nullable public final String getTypeAnonymous(@NonNull android.net.Uri);
method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
method public boolean isChildDocument(String, String);
method public String moveDocument(String, String, String) throws java.io.FileNotFoundException;
@@ -40642,6 +40673,7 @@
public class CredentialEntry implements android.os.Parcelable {
ctor public CredentialEntry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice);
ctor public CredentialEntry(@NonNull android.service.credentials.BeginGetCredentialOption, @NonNull android.app.slice.Slice);
+ ctor public CredentialEntry(@NonNull String, @NonNull android.app.slice.Slice);
method public int describeContents();
method @NonNull public String getBeginGetCredentialOptionId();
method @NonNull public android.app.slice.Slice getSlice();
@@ -41104,6 +41136,7 @@
method @NonNull public String getCardId();
method @NonNull public android.graphics.drawable.Icon getCardImage();
method @Nullable public CharSequence getCardLabel();
+ method @NonNull public java.util.List<android.location.Location> getCardLocations();
method @NonNull public int getCardType();
method @NonNull public CharSequence getContentDescription();
method @Nullable public android.graphics.drawable.Icon getNonPaymentCardSecondaryImage();
@@ -41121,6 +41154,7 @@
method @NonNull public android.service.quickaccesswallet.WalletCard build();
method @NonNull public android.service.quickaccesswallet.WalletCard.Builder setCardIcon(@Nullable android.graphics.drawable.Icon);
method @NonNull public android.service.quickaccesswallet.WalletCard.Builder setCardLabel(@Nullable CharSequence);
+ method @NonNull public android.service.quickaccesswallet.WalletCard.Builder setCardLocations(@NonNull java.util.List<android.location.Location>);
method @NonNull public android.service.quickaccesswallet.WalletCard.Builder setNonPaymentCardSecondaryImage(@Nullable android.graphics.drawable.Icon);
}
@@ -54155,14 +54189,12 @@
method public void addAudioDescriptionRequestedChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
method public boolean addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
method public void addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, @Nullable android.os.Handler);
- method public void addUiContrastChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.UiContrastChangeListener);
method @ColorInt public int getAccessibilityFocusColor();
method public int getAccessibilityFocusStrokeWidth();
method @Deprecated public java.util.List<android.content.pm.ServiceInfo> getAccessibilityServiceList();
method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int);
method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAccessibilityServiceList();
method public int getRecommendedTimeoutMillis(int, int);
- method @FloatRange(from=-1.0F, to=1.0f) public float getUiContrast();
method public void interrupt();
method public static boolean isAccessibilityButtonSupported();
method public boolean isAudioDescriptionRequested();
@@ -54174,7 +54206,6 @@
method public boolean removeAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
method public boolean removeAudioDescriptionRequestedChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
method public boolean removeTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
- method public void removeUiContrastChangeListener(@NonNull android.view.accessibility.AccessibilityManager.UiContrastChangeListener);
method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
field public static final int FLAG_CONTENT_CONTROLS = 4; // 0x4
field public static final int FLAG_CONTENT_ICONS = 1; // 0x1
@@ -54197,10 +54228,6 @@
method public void onTouchExplorationStateChanged(boolean);
}
- public static interface AccessibilityManager.UiContrastChangeListener {
- method public void onUiContrastChanged(@FloatRange(from=-1.0F, to=1.0f) float);
- }
-
public class AccessibilityNodeInfo implements android.os.Parcelable {
ctor public AccessibilityNodeInfo();
ctor public AccessibilityNodeInfo(@NonNull android.view.View);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a8a48b7..fb5ee8d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -834,23 +834,13 @@
}
public class BroadcastOptions {
- method public void clearDeliveryGroupMatchingFilter();
- method public void clearDeliveryGroupMatchingKey();
- method public void clearDeliveryGroupPolicy();
method public void clearRequireCompatChange();
- method @Nullable public android.content.IntentFilter getDeliveryGroupMatchingFilter();
- method @Nullable public String getDeliveryGroupMatchingKey();
- method public int getDeliveryGroupPolicy();
method public int getPendingIntentBackgroundActivityStartMode();
- method public boolean isDeferUntilActive();
+ method @Deprecated public boolean isDeferUntilActive();
method @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed();
- method @Deprecated @NonNull public static android.app.BroadcastOptions makeBasic();
method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
- method @NonNull public android.app.BroadcastOptions setDeferUntilActive(boolean);
- method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingFilter(@NonNull android.content.IntentFilter);
- method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String, @NonNull String);
- method @NonNull public android.app.BroadcastOptions setDeliveryGroupPolicy(int);
+ method @Deprecated @NonNull public android.app.BroadcastOptions setDeferUntilActive(boolean);
method public void setDontSendToRestrictedApps(boolean);
method @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
method @NonNull public android.app.BroadcastOptions setPendingIntentBackgroundActivityStartMode(int);
@@ -859,8 +849,6 @@
method public void setRequireNoneOfPermissions(@Nullable String[]);
method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppAllowlist(long, int, int, @Nullable String);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long);
- field public static final int DELIVERY_GROUP_POLICY_ALL = 0; // 0x0
- field public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1; // 0x1
}
public class DownloadManager {
@@ -2363,6 +2351,8 @@
method @NonNull public String getTargetId();
method @NonNull public java.util.List<java.lang.String> getTargetIds();
method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int ACTION_DELETE = 9; // 0x9
+ field public static final int ACTION_DISMISS = 10; // 0xa
field public static final int ACTION_DRAGNDROP = 7; // 0x7
field public static final int ACTION_LAUNCH_KEYBOARD_FOCUS = 6; // 0x6
field public static final int ACTION_LAUNCH_TOUCH = 5; // 0x5
@@ -3579,9 +3569,7 @@
}
public class IntentFilter implements android.os.Parcelable {
- method @NonNull public final android.os.PersistableBundle getExtras();
method public final int getOrder();
- method public final void setExtras(@NonNull android.os.PersistableBundle);
method public final void setOrder(int);
}
@@ -10141,9 +10129,9 @@
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public boolean disconnectHotspotNetwork(@NonNull android.net.wifi.sharedconnectivity.app.HotspotNetwork);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public boolean forgetKnownNetwork(@NonNull android.net.wifi.sharedconnectivity.app.KnownNetwork);
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus getHotspotNetworkConnectionStatus();
- method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.sharedconnectivity.app.HotspotNetwork> getHotspotNetworks();
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.sharedconnectivity.app.HotspotNetwork> getHotspotNetworks();
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus getKnownNetworkConnectionStatus();
- method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.sharedconnectivity.app.KnownNetwork> getKnownNetworks();
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.sharedconnectivity.app.KnownNetwork> getKnownNetworks();
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState getSettingsState();
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.sharedconnectivity.app.SharedConnectivityClientCallback);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public boolean unregisterCallback(@NonNull android.net.wifi.sharedconnectivity.app.SharedConnectivityClientCallback);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a9ae225..46651a9 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -269,6 +269,7 @@
}
public class BroadcastOptions extends android.app.ComponentOptions {
+ ctor public BroadcastOptions();
ctor public BroadcastOptions(@NonNull android.os.Bundle);
method @Deprecated public int getMaxManifestReceiverApiLevel();
method public long getTemporaryAppAllowlistDuration();
@@ -899,6 +900,7 @@
field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f;
field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
field public static final long OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN = 208648326L; // 0xc6fb886L
+ field public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // 0xe28701fL
field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
}
@@ -1071,6 +1073,40 @@
}
+package android.credentials {
+
+ public final class CredentialManager {
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.QUERY_ALL_PACKAGES, "android.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS"}) public java.util.List<android.credentials.CredentialProviderInfo> getCredentialProviderServicesForTesting(int);
+ method public static boolean isServiceEnabled(@NonNull android.content.Context);
+ field public static final int PROVIDER_FILTER_ALL_PROVIDERS = 0; // 0x0
+ field public static final int PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY = 1; // 0x1
+ field public static final int PROVIDER_FILTER_USER_PROVIDERS_ONLY = 2; // 0x2
+ }
+
+ public final class CredentialProviderInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getCapabilities();
+ method @NonNull public android.content.ComponentName getComponentName();
+ method @Nullable public CharSequence getLabel(@NonNull android.content.Context);
+ method @Nullable public android.graphics.drawable.Drawable getServiceIcon(@NonNull android.content.Context);
+ method @NonNull public android.content.pm.ServiceInfo getServiceInfo();
+ method @NonNull public boolean hasCapability(@NonNull String);
+ method public boolean isEnabled();
+ method public boolean isSystemProvider();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CredentialProviderInfo> CREATOR;
+ }
+
+ public static final class CredentialProviderInfo.Builder {
+ ctor public CredentialProviderInfo.Builder(@NonNull android.content.pm.ServiceInfo);
+ method @NonNull public android.credentials.CredentialProviderInfo.Builder addCapabilities(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.credentials.CredentialProviderInfo build();
+ method @NonNull public android.credentials.CredentialProviderInfo.Builder setEnabled(boolean);
+ method @NonNull public android.credentials.CredentialProviderInfo.Builder setSystemProvider(boolean);
+ }
+
+}
+
package android.credentials.ui {
public final class AuthenticationEntry implements android.os.Parcelable {
@@ -1110,7 +1146,6 @@
public final class Entry implements android.os.Parcelable {
ctor public Entry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice);
- ctor public Entry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, @NonNull android.app.PendingIntent, @NonNull android.content.Intent);
ctor public Entry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, @NonNull android.content.Intent);
method public int describeContents();
method @Nullable public android.content.Intent getFrameworkExtrasIntent();
@@ -1921,6 +1956,10 @@
method public void removeHardwareDevice(int);
}
+ public class TvView extends android.view.ViewGroup {
+ method public void notifyTvMessage(@NonNull String, @NonNull android.os.Bundle);
+ }
+
}
package android.media.tv.tuner {
@@ -3303,7 +3342,9 @@
}
@UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
+ method public void getBoundsOnScreen(@NonNull android.graphics.Rect, boolean);
method public android.view.View getTooltipView();
+ method public void getWindowDisplayFrame(@NonNull android.graphics.Rect);
method public boolean isAutofilled();
method public static boolean isDefaultFocusHighlightEnabled();
method public boolean isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
@@ -3936,6 +3977,18 @@
method public abstract void onTransactionReady(int, @NonNull android.view.SurfaceControl.Transaction);
}
+ public class WindowInfosListenerForTest {
+ ctor public WindowInfosListenerForTest();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public void addWindowInfosListener(@NonNull java.util.function.Consumer<java.util.List<android.window.WindowInfosListenerForTest.WindowInfo>>);
+ method public void removeWindowInfosListener(@NonNull java.util.function.Consumer<java.util.List<android.window.WindowInfosListenerForTest.WindowInfo>>);
+ }
+
+ public static class WindowInfosListenerForTest.WindowInfo {
+ field @NonNull public final android.graphics.Rect bounds;
+ field @NonNull public final String name;
+ field @NonNull public final android.os.IBinder windowToken;
+ }
+
public class WindowOrganizer {
ctor public WindowOrganizer();
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback);
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 738e2de..3969577 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -285,6 +285,28 @@
],
}
+filegroup {
+ name: "hwbinder-stubs-srcs",
+ srcs: [
+ "android/os/HidlSupport.java",
+ "android/os/HidlMemory.java",
+ "android/os/HwBinder.java",
+ "android/os/HwBlob.java",
+ "android/os/HwParcel.java",
+ "android/os/IHwBinder.java",
+ "android/os/IHwInterface.java",
+ "android/os/DeadObjectException.java",
+ "android/os/DeadSystemException.java",
+ "android/os/NativeHandle.java",
+ "android/os/RemoteException.java",
+ "android/util/AndroidException.java",
+ ],
+ visibility: [
+ "//frameworks/base",
+ "//frameworks/base/api",
+ ],
+}
+
cc_defaults {
name: "incremental_default",
cflags: [
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 87fe215..b4068db 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4208,8 +4208,8 @@
* processes to reclaim memory; the system will take care of restarting
* these processes in the future as needed.
*
- * <p class="note">On devices with a {@link Build.VERSION#SECURITY_PATCH} of 2022-12-01 or
- * greater, third party applications can only use this API to kill their own processes.
+ * <p class="note">On devices that run Android 14 or higher,
+ * third party applications can only use this API to kill their own processes.
* </p>
*
* @param packageName The name of the package whose processes are to
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 9bf9e0c..7c32c9c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -7200,10 +7200,16 @@
if (mContext != null) {
final PackageManager pm = mContext.getPackageManager();
try {
- if (pm != null && pm.checkPermission(Manifest.permission.READ_DEVICE_CONFIG,
- mContext.getPackageName()) == PackageManager.PERMISSION_GRANTED) {
- DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY,
- mContext.getMainExecutor(), properties -> {
+ if (Build.IS_ENG
+ && pm != null
+ && pm.checkPermission(
+ Manifest.permission.READ_DEVICE_CONFIG,
+ mContext.getPackageName())
+ == PackageManager.PERMISSION_GRANTED) {
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ mContext.getMainExecutor(),
+ properties -> {
if (properties.getKeyset().contains(FULL_LOG)) {
sFullLog = properties.getBoolean(FULL_LOG, false);
}
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index f48181b..481f671 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -64,6 +64,7 @@
private @Nullable String mDeliveryGroupMatchingKey;
private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
private @Nullable IntentFilter mDeliveryGroupMatchingFilter;
+ private @DeferralPolicy int mDeferralPolicy;
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
@@ -71,8 +72,8 @@
FLAG_ALLOW_BACKGROUND_ACTIVITY_STARTS,
FLAG_REQUIRE_COMPAT_CHANGE_ENABLED,
FLAG_IS_ALARM_BROADCAST,
- FLAG_IS_DEFER_UNTIL_ACTIVE,
FLAG_SHARE_IDENTITY,
+ FLAG_INTERACTIVE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Flags {}
@@ -81,8 +82,8 @@
private static final int FLAG_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1 << 1;
private static final int FLAG_REQUIRE_COMPAT_CHANGE_ENABLED = 1 << 2;
private static final int FLAG_IS_ALARM_BROADCAST = 1 << 3;
- private static final int FLAG_IS_DEFER_UNTIL_ACTIVE = 1 << 4;
- private static final int FLAG_SHARE_IDENTITY = 1 << 5;
+ private static final int FLAG_SHARE_IDENTITY = 1 << 4;
+ private static final int FLAG_INTERACTIVE = 1 << 5;
/**
* Change ID which is invalid.
@@ -213,11 +214,17 @@
"android:broadcast.deliveryGroupMatchingFilter";
/**
+ * Corresponds to {@link #setDeferralPolicy(int)}
+ */
+ private static final String KEY_DEFERRAL_POLICY =
+ "android:broadcast.deferralPolicy";
+
+ /**
* The list of delivery group policies which specify how multiple broadcasts belonging to
* the same delivery group has to be handled.
* @hide
*/
- @IntDef(flag = true, prefix = { "DELIVERY_GROUP_POLICY_" }, value = {
+ @IntDef(prefix = { "DELIVERY_GROUP_POLICY_" }, value = {
DELIVERY_GROUP_POLICY_ALL,
DELIVERY_GROUP_POLICY_MOST_RECENT,
DELIVERY_GROUP_POLICY_MERGED,
@@ -228,19 +235,13 @@
/**
* Delivery group policy that indicates that all the broadcasts in the delivery group
* need to be delivered as is.
- *
- * @hide
*/
- @SystemApi
public static final int DELIVERY_GROUP_POLICY_ALL = 0;
/**
* Delivery group policy that indicates that only the most recent broadcast in the delivery
* group need to be delivered and the rest can be dropped.
- *
- * @hide
*/
- @SystemApi
public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1;
/**
@@ -251,24 +252,62 @@
*/
public static final int DELIVERY_GROUP_POLICY_MERGED = 2;
+ /** {@hide} */
+ @IntDef(prefix = { "DEFERRAL_POLICY_" }, value = {
+ DEFERRAL_POLICY_DEFAULT,
+ DEFERRAL_POLICY_NONE,
+ DEFERRAL_POLICY_UNTIL_ACTIVE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeferralPolicy {}
+
+ /**
+ * Deferral policy that indicates no desire has been expressed, and that the
+ * system should use a reasonable default behavior.
+ */
+ public static final int DEFERRAL_POLICY_DEFAULT = 0;
+
+ /**
+ * Deferral policy that indicates a strong desire that no receiver of this
+ * broadcast should be deferred.
+ */
+ public static final int DEFERRAL_POLICY_NONE = 1;
+
+ /**
+ * Deferral policy that indicates a strong desire that each receiver of this
+ * broadcast should be deferred until that receiver's process is in an
+ * active (non-cached) state. Whether an app's process state is considered
+ * active is independent of its standby bucket.
+ * <p>
+ * This policy only applies to runtime registered receivers of a broadcast,
+ * and does not apply to ordered broadcasts, alarm broadcasts, interactive
+ * broadcasts, or manifest broadcasts.
+ * <p>
+ * This policy means that a runtime registered receiver will not typically
+ * execute until that receiver's process is brought to an active state by
+ * some other action, such as a job, alarm, or service binding. As a result,
+ * the receiver may be delayed indefinitely.
+ * <p>
+ * When this policy is set on an unordered broadcast with a completion
+ * callback, the completion callback will run once all eligible processes
+ * have finished receiving the broadcast. Processes in inactive process
+ * state are not considered eligible and may not receive the broadcast prior
+ * to the completion callback.
+ */
+ public static final int DEFERRAL_POLICY_UNTIL_ACTIVE = 2;
+
/**
* Creates a basic {@link BroadcastOptions} with no options initially set.
*
* @return an instance of {@code BroadcastOptions} against which options can be set
- *
- * @deprecated Use {@link BroadcastOptions#BroadcastOptions()} instead.
- * @hide
*/
- @Deprecated
- @SystemApi
public static @NonNull BroadcastOptions makeBasic() {
BroadcastOptions opts = new BroadcastOptions();
return opts;
}
- /**
- * Creates a new {@code BroadcastOptions} with no options initially set.
- */
+ /** @hide */
+ @TestApi
public BroadcastOptions() {
super();
resetTemporaryAppAllowlist();
@@ -303,6 +342,7 @@
BundleMerger.class);
mDeliveryGroupMatchingFilter = opts.getParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER,
IntentFilter.class);
+ mDeferralPolicy = opts.getInt(KEY_DEFERRAL_POLICY, DEFERRAL_POLICY_DEFAULT);
}
/**
@@ -728,60 +768,56 @@
return mIdForResponseEvent;
}
- /**
- * Sets whether the broadcast should not run until the process is in an active process state
- * (ie, a process exists for the app and the app is not in a cached process state).
- *
- * Whether an app's process state is considered active is independent of its standby bucket.
- *
- * A broadcast that is deferred until the process is active will not execute until the process
- * is brought to an active state by some other action, like a job, alarm, or service binding. As
- * a result, the broadcast may be delayed indefinitely. This deferral only applies to runtime
- * registered receivers of a broadcast. Any manifest receivers will run immediately, similar to
- * how a manifest receiver would start a new process in order to run a broadcast receiver.
- *
- * Ordered broadcasts, alarm broadcasts, interactive broadcasts, and manifest broadcasts are
- * never deferred.
- *
- * Unordered broadcasts and unordered broadcasts with completion callbacks may be
- * deferred. Completion callbacks for broadcasts deferred until active are
- * best-effort. Completion callbacks will run when all eligible processes have finished
- * executing the broadcast. Processes in inactive process states that defer the broadcast are
- * not considered eligible and may not execute the broadcast prior to the completion callback.
- *
- * @hide
- */
+ /** {@hide} */
@SystemApi
+ @Deprecated
+ // STOPSHIP: remove entirely after this API change lands in AOSP
public @NonNull BroadcastOptions setDeferUntilActive(boolean shouldDefer) {
if (shouldDefer) {
- mFlags |= FLAG_IS_DEFER_UNTIL_ACTIVE;
+ setDeferralPolicy(DEFERRAL_POLICY_UNTIL_ACTIVE);
} else {
- mFlags &= ~FLAG_IS_DEFER_UNTIL_ACTIVE;
+ setDeferralPolicy(DEFERRAL_POLICY_NONE);
}
return this;
}
- /**
- * Returns if this broadcast should not run until the process is in an active process state.
- *
- * @return {@code true} if this broadcast should not run until the process is in an active
- * process state. Otherwise, {@code false}.
- * @see #setDeferUntilActive(boolean)
- *
- * @hide
- */
+ /** {@hide} */
@SystemApi
+ @Deprecated
+ // STOPSHIP: remove entirely after this API change lands in AOSP
public boolean isDeferUntilActive() {
- return (mFlags & FLAG_IS_DEFER_UNTIL_ACTIVE) != 0;
+ return (mDeferralPolicy == DEFERRAL_POLICY_UNTIL_ACTIVE);
+ }
+
+ /**
+ * Sets deferral policy for this broadcast that specifies how this broadcast
+ * can be deferred for delivery at some future point.
+ */
+ public @NonNull BroadcastOptions setDeferralPolicy(@DeferralPolicy int deferralPolicy) {
+ mDeferralPolicy = deferralPolicy;
+ return this;
+ }
+
+ /**
+ * Gets deferral policy for this broadcast that specifies how this broadcast
+ * can be deferred for delivery at some future point.
+ */
+ public @DeferralPolicy int getDeferralPolicy() {
+ return mDeferralPolicy;
+ }
+
+ /**
+ * Clears any deferral policy for this broadcast that specifies how this
+ * broadcast can be deferred for delivery at some future point.
+ */
+ public void clearDeferralPolicy() {
+ mDeferralPolicy = DEFERRAL_POLICY_DEFAULT;
}
/**
* Set delivery group policy for this broadcast to specify how multiple broadcasts belonging to
* the same delivery group has to be handled.
- *
- * @hide
*/
- @SystemApi
@NonNull
public BroadcastOptions setDeliveryGroupPolicy(@DeliveryGroupPolicy int policy) {
mDeliveryGroupPolicy = policy;
@@ -791,10 +827,7 @@
/**
* Get the delivery group policy for this broadcast that specifies how multiple broadcasts
* belonging to the same delivery group has to be handled.
- *
- * @hide
*/
- @SystemApi
public @DeliveryGroupPolicy int getDeliveryGroupPolicy() {
return mDeliveryGroupPolicy;
}
@@ -803,10 +836,7 @@
* Clears any previously set delivery group policies using
* {@link #setDeliveryGroupMatchingKey(String, String)} and resets the delivery group policy to
* the default value ({@link #DELIVERY_GROUP_POLICY_ALL}).
- *
- * @hide
*/
- @SystemApi
public void clearDeliveryGroupPolicy() {
mDeliveryGroupPolicy = DELIVERY_GROUP_POLICY_ALL;
}
@@ -821,10 +851,7 @@
* <p> If neither matching key using this API nor matching filter using
* {@link #setDeliveryGroupMatchingFilter(IntentFilter)} is specified, then by default
* {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
- *
- * @hide
*/
- @SystemApi
@NonNull
public BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String namespace,
@NonNull String key) {
@@ -842,9 +869,7 @@
*
* @return the delivery group namespace and key that was previously set using
* {@link #setDeliveryGroupMatchingKey(String, String)}, concatenated with a {@code :}.
- * @hide
*/
- @SystemApi
@Nullable
public String getDeliveryGroupMatchingKey() {
return mDeliveryGroupMatchingKey;
@@ -853,10 +878,7 @@
/**
* Clears the namespace and key that was previously set using
* {@link #setDeliveryGroupMatchingKey(String, String)}.
- *
- * @hide
*/
- @SystemApi
public void clearDeliveryGroupMatchingKey() {
mDeliveryGroupMatchingKey = null;
}
@@ -871,10 +893,7 @@
* <p> If neither matching key using {@link #setDeliveryGroupMatchingKey(String, String)} nor
* matching filter using this API is specified, then by default
* {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
- *
- * @hide
*/
- @SystemApi
@NonNull
public BroadcastOptions setDeliveryGroupMatchingFilter(@NonNull IntentFilter matchingFilter) {
mDeliveryGroupMatchingFilter = Objects.requireNonNull(matchingFilter);
@@ -887,9 +906,7 @@
*
* @return the {@link IntentFilter} object that was previously set using
* {@link #setDeliveryGroupMatchingFilter(IntentFilter)}.
- * @hide
*/
- @SystemApi
@Nullable
public IntentFilter getDeliveryGroupMatchingFilter() {
return mDeliveryGroupMatchingFilter;
@@ -898,10 +915,7 @@
/**
* Clears the {@link IntentFilter} object that was previously set using
* {@link #setDeliveryGroupMatchingFilter(IntentFilter)}.
- *
- * @hide
*/
- @SystemApi
public void clearDeliveryGroupMatchingFilter() {
mDeliveryGroupMatchingFilter = null;
}
@@ -944,6 +958,36 @@
}
/**
+ * Sets whether the broadcast should be considered as having originated from
+ * some direct interaction by the user such as a notification tap or button
+ * press. This signal is used internally to ensure the broadcast is
+ * delivered quickly with low latency.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE)
+ public @NonNull BroadcastOptions setInteractive(boolean interactive) {
+ if (interactive) {
+ mFlags |= FLAG_INTERACTIVE;
+ } else {
+ mFlags &= ~FLAG_INTERACTIVE;
+ }
+ return this;
+ }
+
+ /**
+ * Returns whether the broadcast should be considered as having originated
+ * from some direct interaction by the user such as a notification tap or
+ * button press.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE)
+ public boolean isInteractive() {
+ return (mFlags & FLAG_INTERACTIVE) != 0;
+ }
+
+ /**
* Set PendingIntent activity is allowed to be started in the background if the caller
* can start background activities.
*
@@ -1064,11 +1108,22 @@
if (mDeliveryGroupMatchingFilter != null) {
b.putParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER, mDeliveryGroupMatchingFilter);
}
- return b.isEmpty() ? null : b;
+ if (mDeferralPolicy != DEFERRAL_POLICY_DEFAULT) {
+ b.putInt(KEY_DEFERRAL_POLICY, mDeferralPolicy);
+ }
+ return b;
}
- /** @hide */
- public static @Nullable BroadcastOptions fromBundle(@Nullable Bundle options) {
+ /**
+ * Returns a {@link BroadcastOptions} parsed from the given {@link Bundle},
+ * typically generated from {@link #toBundle()}.
+ */
+ public static @NonNull BroadcastOptions fromBundle(@NonNull Bundle options) {
+ return new BroadcastOptions(options);
+ }
+
+ /** {@hide} */
+ public static @Nullable BroadcastOptions fromBundleNullable(@Nullable Bundle options) {
return (options != null) ? new BroadcastOptions(options) : null;
}
}
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
index ceb9e90..e0e2855 100644
--- a/core/java/android/app/ComponentOptions.java
+++ b/core/java/android/app/ComponentOptions.java
@@ -19,7 +19,6 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.os.Bundle;
@@ -53,15 +52,8 @@
public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION =
"android.pendingIntent.backgroundActivityAllowedByPermission";
- /**
- * Corresponds to {@link #setInteractive(boolean)}
- * @hide
- */
- private static final String KEY_INTERACTIVE = "android:component.isInteractive";
-
private @Nullable Boolean mPendingIntentBalAllowed = null;
private boolean mPendingIntentBalAllowedByPermission = false;
- private boolean mIsInteractive = false;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -106,29 +98,6 @@
setPendingIntentBackgroundActivityLaunchAllowedByPermission(
opts.getBoolean(
KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, false));
- mIsInteractive = opts.getBoolean(KEY_INTERACTIVE, false);
- }
-
- /**
- * When set, a broadcast will be understood as having originated from
- * some direct interaction by the user such as a notification tap or button
- * press. Only the OS itself may use this option.
- * @hide
- * @param interactive
- * @see #isInteractive()
- */
- @RequiresPermission(android.Manifest.permission.COMPONENT_OPTION_INTERACTIVE)
- public void setInteractive(boolean interactive) {
- mIsInteractive = interactive;
- }
-
- /**
- * Did this PendingIntent send originate with a direct user interaction?
- * @return true if this is the result of an interaction, false otherwise
- * @hide
- */
- public boolean isInteractive() {
- return mIsInteractive;
}
/**
@@ -232,9 +201,6 @@
b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION,
mPendingIntentBalAllowedByPermission);
}
- if (mIsInteractive) {
- b.putBoolean(KEY_INTERACTIVE, true);
- }
return b;
}
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 2242224b..2345c27 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -17,6 +17,7 @@
package android.app;
import android.app.IOnProjectionStateChangedListener;
+import android.app.IUiModeManagerCallback;
/**
* Interface used to control special UI modes.
@@ -24,6 +25,11 @@
*/
interface IUiModeManager {
/**
+ * @hide
+ */
+ void addCallback(IUiModeManagerCallback callback);
+
+ /**
* Enables the car mode. Only the system can do this.
* @hide
*/
@@ -173,4 +179,9 @@
* Returns currently set projection types.
*/
int getActiveProjectionTypes();
+
+ /**
+ * Returns the contrast for the current user
+ */
+ float getContrast();
}
diff --git a/core/java/android/credentials/IListEnabledProvidersCallback.aidl b/core/java/android/app/IUiModeManagerCallback.aidl
similarity index 60%
copy from core/java/android/credentials/IListEnabledProvidersCallback.aidl
copy to core/java/android/app/IUiModeManagerCallback.aidl
index 3a8e25ed..47c18a8 100644
--- a/core/java/android/credentials/IListEnabledProvidersCallback.aidl
+++ b/core/java/android/app/IUiModeManagerCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 The Android Open Source Project
+ * Copyright (C) 2023 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,16 +14,14 @@
* limitations under the License.
*/
-package android.credentials;
-
-import android.credentials.ListEnabledProvidersResponse;
+package android.app;
/**
- * Listener for an listEnabledProviders request.
- *
- * @hide
- */
-interface IListEnabledProvidersCallback {
- oneway void onResponse(in ListEnabledProvidersResponse response);
- oneway void onError(String errorType, String message);
-}
\ No newline at end of file
+* Implemented by the UiModeManager client to receive information about changes from the service.
+* This is a oneway interface since the server should not block waiting for the client.
+*
+* @hide
+*/
+oneway interface IUiModeManagerCallback {
+ void notifyContrastChanged(float contrast);
+}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 5cad1ae..ac92811 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -93,7 +93,7 @@
private static final String TAG = "Instrumentation";
- private static final long CONNECT_TIMEOUT_MILLIS = 5000;
+ private static final long CONNECT_TIMEOUT_MILLIS = 60_000;
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index ecab37d..d90257a 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -17,6 +17,7 @@
package android.app;
import android.annotation.CallbackExecutor;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -34,6 +35,7 @@
import android.os.ServiceManager.ServiceNotFoundException;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -43,10 +45,13 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.time.LocalTime;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.stream.Stream;
/**
* This class provides access to the system uimode services. These services
@@ -72,6 +77,10 @@
*/
@SystemService(Context.UI_MODE_SERVICE)
public class UiModeManager {
+
+ private static final String TAG = "UiModeManager";
+
+
/**
* A listener with a single method that is invoked whenever the packages projecting using the
* {@link ProjectionType}s for which it is registered change.
@@ -91,7 +100,20 @@
@NonNull Set<String> packageNames);
}
- private static final String TAG = "UiModeManager";
+ /**
+ * Listener for the UI contrast. To listen for changes to
+ * the UI contrast on the device, implement this interface and
+ * register it with the system by calling {@link #addContrastChangeListener}.
+ */
+ public interface ContrastChangeListener {
+
+ /**
+ * Called when the color contrast enabled state changes.
+ *
+ * @param contrast The color contrast as in {@link #getContrast}
+ */
+ void onContrastChanged(@FloatRange(from = -1.0f, to = 1.0f) float contrast);
+ }
/**
* Broadcast sent when the device's UI has switched to car mode, either
@@ -319,6 +341,95 @@
mOnProjectionStateChangedListenerResourceManager =
new OnProjectionStateChangedListenerResourceManager();
+ /**
+ * Define constants and conversions between {@link ContrastLevel}s and contrast values.
+ * <p>
+ * Contrast values are floats defined in [-1, 1], as defined in {@link #getContrast}.
+ * This is the official data type for contrast;
+ * all methods from the public API return contrast values.
+ * </p>
+ * <p>
+ * {@code ContrastLevel}, on the other hand, is an internal-only enumeration of contrasts that
+ * can be set from the system ui. Each {@code ContrastLevel} has an associated contrast value.
+ * </p>
+ * <p>
+ * Currently, a user chan chose from three contrast levels:
+ * <ul>
+ * <li>{@link #CONTRAST_LEVEL_STANDARD}, corresponding to the default contrast value 0f</li>
+ * <li>{@link #CONTRAST_LEVEL_MEDIUM}, corresponding to the contrast value 0.5f</li>
+ * <li>{@link #CONTRAST_LEVEL_HIGH}, corresponding to the maximum contrast value 1f</li>
+ * </ul>
+ * </p>
+ *
+ * @hide
+ */
+ public static class ContrastUtils {
+
+ private static final float CONTRAST_MIN_VALUE = -1f;
+ private static final float CONTRAST_MAX_VALUE = 1f;
+ public static final float CONTRAST_DEFAULT_VALUE = 0f;
+
+ @IntDef(flag = true, prefix = { "CONTRAST_LEVEL_" }, value = {
+ CONTRAST_LEVEL_STANDARD,
+ CONTRAST_LEVEL_MEDIUM,
+ CONTRAST_LEVEL_HIGH
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ContrastLevel {}
+
+ public static final int CONTRAST_LEVEL_STANDARD = 0;
+ public static final int CONTRAST_LEVEL_MEDIUM = 1;
+ public static final int CONTRAST_LEVEL_HIGH = 2;
+
+ private static Stream<Integer> allContrastLevels() {
+ return Stream.of(CONTRAST_LEVEL_STANDARD, CONTRAST_LEVEL_MEDIUM, CONTRAST_LEVEL_HIGH);
+ }
+
+ /**
+ * Convert a contrast value in [-1, 1] to its associated {@link ContrastLevel}
+ */
+ public static @ContrastLevel int toContrastLevel(float contrast) {
+ if (contrast < CONTRAST_MIN_VALUE || contrast > CONTRAST_MAX_VALUE) {
+ throw new IllegalArgumentException("contrast values should be in [-1, 1]");
+ }
+ return allContrastLevels().min(Comparator.comparingDouble(contrastLevel ->
+ Math.abs(contrastLevel - 2 * contrast))).orElseThrow();
+ }
+
+ /**
+ * Convert a {@link ContrastLevel} to its associated contrast value in [-1, 1]
+ */
+ public static float fromContrastLevel(@ContrastLevel int contrastLevel) {
+ if (allContrastLevels().noneMatch(level -> level == contrastLevel)) {
+ throw new IllegalArgumentException("unrecognized contrast level: " + contrastLevel);
+ }
+ return contrastLevel / 2f;
+ }
+ }
+
+ /**
+ * Map that stores user provided {@link ContrastChangeListener} callbacks,
+ * and the executors on which these callbacks should be called.
+ */
+ private final ArrayMap<ContrastChangeListener, Executor>
+ mContrastChangeListeners = new ArrayMap<>();
+ private float mContrast;
+
+ private final IUiModeManagerCallback.Stub mCallback = new IUiModeManagerCallback.Stub() {
+ @Override
+ public void notifyContrastChanged(float contrast) {
+ final ArrayMap<ContrastChangeListener, Executor> listeners;
+ synchronized (mLock) {
+ // if value changed in the settings, update the cached value and notify listeners
+ if (Math.abs(mContrast - contrast) < 1e-10) return;
+ mContrast = contrast;
+ listeners = new ArrayMap<>(mContrastChangeListeners);
+ }
+ listeners.forEach((listener, executor) -> executor.execute(
+ () -> listener.onContrastChanged(mContrast)));
+ }
+ };
+
@UnsupportedAppUsage
/*package*/ UiModeManager() throws ServiceNotFoundException {
this(null /* context */);
@@ -328,6 +439,12 @@
mService = IUiModeManager.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.UI_MODE_SERVICE));
mContext = context;
+ try {
+ mService.addCallback(mCallback);
+ mContrast = mService.getContrast();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Setup failed: UiModeManagerService is dead", e);
+ }
}
/**
@@ -1067,4 +1184,56 @@
return mExecutorMap.get(innerListener);
}
}
+
+ /**
+ * Returns the color contrast for the user.
+ * <p>
+ * <strong>Note:</strong> You need to query this only if your application is
+ * doing its own rendering and does not rely on the material rendering pipeline.
+ * </p>
+ * @return The color contrast, float in [-1, 1] where
+ * <ul>
+ * <li> 0 corresponds to the default contrast </li>
+ * <li> -1 corresponds to the minimum contrast </li>
+ * <li> 1 corresponds to the maximum contrast </li>
+ * </ul>
+ *
+ *
+ *
+ */
+ @FloatRange(from = -1.0f, to = 1.0f)
+ public float getContrast() {
+ synchronized (mLock) {
+ return mContrast;
+ }
+ }
+
+ /**
+ * Registers a {@link ContrastChangeListener} for the current user.
+ *
+ * @param executor The executor on which the listener should be called back.
+ * @param listener The listener.
+ */
+ public void addContrastChangeListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull ContrastChangeListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ synchronized (mLock) {
+ mContrastChangeListeners.put(listener, executor);
+ }
+ }
+
+ /**
+ * Unregisters a {@link ContrastChangeListener} for the current user.
+ * If the listener was not registered, does nothing and returns.
+ *
+ * @param listener The listener to unregister.
+ */
+ public void removeContrastChangeListener(@NonNull ContrastChangeListener listener) {
+ Objects.requireNonNull(listener);
+ synchronized (mLock) {
+ mContrastChangeListeners.remove(listener);
+ }
+ }
}
diff --git a/core/java/android/app/admin/DevicePolicyCache.java b/core/java/android/app/admin/DevicePolicyCache.java
index 3957732..b6e83c8 100644
--- a/core/java/android/app/admin/DevicePolicyCache.java
+++ b/core/java/android/app/admin/DevicePolicyCache.java
@@ -19,6 +19,9 @@
import com.android.server.LocalServices;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Stores a copy of the set of device policies maintained by {@link DevicePolicyManager} that
* can be accessed from any place without risking dead locks.
@@ -61,6 +64,12 @@
public abstract boolean canAdminGrantSensorsPermissions();
/**
+ * Returns a list of package names for which all launcher shortcuts should be modified to be
+ * launched in the managed profile and badged accordingly.
+ */
+ public abstract List<String> getLauncherShortcutOverrides();
+
+ /**
* Empty implementation.
*/
private static class EmptyDevicePolicyCache extends DevicePolicyCache {
@@ -85,5 +94,9 @@
public boolean canAdminGrantSensorsPermissions() {
return false;
}
+ @Override
+ public List<String> getLauncherShortcutOverrides() {
+ return new ArrayList<>();
+ }
}
}
diff --git a/core/java/android/app/search/SearchTargetEvent.java b/core/java/android/app/search/SearchTargetEvent.java
index d4915af..e8ef922 100644
--- a/core/java/android/app/search/SearchTargetEvent.java
+++ b/core/java/android/app/search/SearchTargetEvent.java
@@ -50,7 +50,9 @@
ACTION_LAUNCH_TOUCH,
ACTION_LAUNCH_KEYBOARD_FOCUS,
ACTION_DRAGNDROP,
- ACTION_SURFACE_INVISIBLE
+ ACTION_SURFACE_INVISIBLE,
+ ACTION_DELETE,
+ ACTION_DISMISS
})
@Retention(RetentionPolicy.SOURCE)
public @interface ActionType {}
@@ -114,6 +116,16 @@
*/
public static final int ACTION_SURFACE_INVISIBLE = 8;
+ /**
+ * Constant that defines user deleted a target.
+ */
+ public static final int ACTION_DELETE = 9;
+
+ /**
+ * Constant that defines user dismissed a target.
+ */
+ public static final int ACTION_DISMISS = 10;
+
private SearchTargetEvent(@NonNull List<String> targetIds,
@Nullable String location,
@ActionType int actionType,
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index afc2285..5928a50 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -2204,9 +2204,7 @@
* <p> Subsequent calls to this method will override any previously set extras.
*
* @param extras The intent extras to match against.
- * @hide
*/
- @SystemApi
public final void setExtras(@NonNull PersistableBundle extras) {
mExtras = extras;
}
@@ -2216,11 +2214,8 @@
*
* @return the extras that were previously set using {@link #setExtras(PersistableBundle)} or
* an empty {@link PersistableBundle} object if no extras were set.
- * @hide
*/
- @SystemApi
- @NonNull
- public final PersistableBundle getExtras() {
+ public final @NonNull PersistableBundle getExtras() {
return mExtras == null ? new PersistableBundle() : mExtras;
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index f8f2663..eb14cc4 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1156,6 +1156,34 @@
264301586L; // buganizer id
/**
+ * This change id forces the packages it is applied to sandbox {@link android.view.View} API to
+ * an activity bounds for:
+ *
+ * <p>{@link android.view.View#getLocationOnScreen},
+ * {@link android.view.View#getWindowVisibleDisplayFrame},
+ * {@link android.view.View}#getWindowDisplayFrame,
+ * {@link android.view.View}#getBoundsOnScreen.
+ *
+ * <p>For {@link android.view.View#getWindowVisibleDisplayFrame} and
+ * {@link android.view.View}#getWindowDisplayFrame this sandboxing is happening indirectly
+ * through
+ * {@link android.view.ViewRootImpl}#getWindowVisibleDisplayFrame,
+ * {@link android.view.ViewRootImpl}#getDisplayFrame respectively.
+ *
+ * <p>Some applications assume that they occupy the whole screen and therefore use the display
+ * coordinates in their calculations as if an activity is positioned in the top-left corner of
+ * the screen, with left coordinate equal to 0. This may not be the case of applications in
+ * multi-window and in letterbox modes. This can lead to shifted or out of bounds UI elements in
+ * case the activity is Letterboxed or is in multi-window mode.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // buganizer id
+
+ /**
* This change id is the gatekeeper for all treatments that force a given min aspect ratio.
* Enabling this change will allow the following min aspect ratio treatments to be applied:
* OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 08cfbf7..96a42e2 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -33,6 +33,7 @@
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
+import android.content.pm.LauncherActivityInfoInternal;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.UserHandle;
@@ -114,4 +115,5 @@
String getShortcutIconUri(String callingPackage, String packageName, String shortcutId,
int userId);
+ Map<String, LauncherActivityInfoInternal> getActivityOverrides(String callingPackage, int userId);
}
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index 16e720e..a4d5327 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -34,7 +34,6 @@
*/
public class LauncherActivityInfo {
private final PackageManager mPm;
- private UserHandle mUser;
private final LauncherActivityInfoInternal mInternal;
/**
@@ -43,9 +42,8 @@
* @param context The context for fetching resources.
*/
- LauncherActivityInfo(Context context, UserHandle user, LauncherActivityInfoInternal internal) {
+ LauncherActivityInfo(Context context, LauncherActivityInfoInternal internal) {
mPm = context.getPackageManager();
- mUser = user;
mInternal = internal;
}
@@ -70,7 +68,7 @@
* @return The UserHandle of the profile.
*/
public UserHandle getUser() {
- return mUser;
+ return mInternal.getUser();
}
/**
@@ -180,6 +178,6 @@
public Drawable getBadgedIcon(int density) {
Drawable originalIcon = getIcon(density);
- return mPm.getUserBadgedIcon(originalIcon, mUser);
+ return mPm.getUserBadgedIcon(originalIcon, mInternal.getUser());
}
}
diff --git a/core/java/android/content/pm/LauncherActivityInfoInternal.java b/core/java/android/content/pm/LauncherActivityInfoInternal.java
index 46c415d..5aac97d 100644
--- a/core/java/android/content/pm/LauncherActivityInfoInternal.java
+++ b/core/java/android/content/pm/LauncherActivityInfoInternal.java
@@ -21,6 +21,7 @@
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
/**
* @hide
@@ -30,23 +31,27 @@
@NonNull private ActivityInfo mActivityInfo;
@NonNull private ComponentName mComponentName;
@NonNull private IncrementalStatesInfo mIncrementalStatesInfo;
+ @NonNull private UserHandle mUser;
/**
* @param info ActivityInfo from which to create the LauncherActivityInfo.
* @param incrementalStatesInfo The package's states.
+ * @param user The user the activity info belongs to.
*/
public LauncherActivityInfoInternal(@NonNull ActivityInfo info,
- @NonNull IncrementalStatesInfo incrementalStatesInfo) {
+ @NonNull IncrementalStatesInfo incrementalStatesInfo,
+ @NonNull UserHandle user) {
mActivityInfo = info;
mComponentName = new ComponentName(info.packageName, info.name);
mIncrementalStatesInfo = incrementalStatesInfo;
+ mUser = user;
}
public LauncherActivityInfoInternal(Parcel source) {
- mActivityInfo = source.readParcelable(ActivityInfo.class.getClassLoader(), android.content.pm.ActivityInfo.class);
+ mActivityInfo = source.readTypedObject(ActivityInfo.CREATOR);
mComponentName = new ComponentName(mActivityInfo.packageName, mActivityInfo.name);
- mIncrementalStatesInfo = source.readParcelable(
- IncrementalStatesInfo.class.getClassLoader(), android.content.pm.IncrementalStatesInfo.class);
+ mIncrementalStatesInfo = source.readTypedObject(IncrementalStatesInfo.CREATOR);
+ mUser = source.readTypedObject(UserHandle.CREATOR);
}
public ComponentName getComponentName() {
@@ -57,6 +62,10 @@
return mActivityInfo;
}
+ public UserHandle getUser() {
+ return mUser;
+ }
+
public IncrementalStatesInfo getIncrementalStatesInfo() {
return mIncrementalStatesInfo;
}
@@ -68,8 +77,9 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelable(mActivityInfo, 0);
- dest.writeParcelable(mIncrementalStatesInfo, 0);
+ dest.writeTypedObject(mActivityInfo, flags);
+ dest.writeTypedObject(mIncrementalStatesInfo, flags);
+ dest.writeTypedObject(mUser, flags);
}
public static final @android.annotation.NonNull Creator<LauncherActivityInfoInternal> CREATOR =
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index f8c4974..8989006 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -64,6 +64,7 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
@@ -793,13 +794,45 @@
if (ai == null) {
return null;
}
- return new LauncherActivityInfo(mContext, user, ai);
+ return new LauncherActivityInfo(mContext, ai);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
+ * Returns overrides for the activities that should be launched for the shortcuts of certain
+ * package names.
+ *
+ * @return {@link Map} whose keys are package names and whose values are the
+ * {@link LauncherActivityInfo}s that should be used for those packages' shortcuts. If there are
+ * no activity overrides, an empty {@link Map} will be returned.
+ *
+ * @hide
+ */
+ @NonNull
+ public Map<String, LauncherActivityInfo> getActivityOverrides() {
+ Map<String, LauncherActivityInfo> activityOverrides = new ArrayMap<>();
+ try {
+ Map<String, LauncherActivityInfoInternal> activityOverridesInternal =
+ mService.getActivityOverrides(mContext.getPackageName(), mContext.getUserId());
+ for (Map.Entry<String, LauncherActivityInfoInternal> packageToOverride :
+ activityOverridesInternal.entrySet()) {
+ activityOverrides.put(
+ packageToOverride.getKey(),
+ new LauncherActivityInfo(
+ mContext,
+ packageToOverride.getValue()
+ )
+ );
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ return activityOverrides;
+ }
+
+ /**
* Starts a Main activity in the specified profile.
*
* @param component The ComponentName of the activity to launch
@@ -916,7 +949,7 @@
}
ArrayList<LauncherActivityInfo> lais = new ArrayList<>();
for (LauncherActivityInfoInternal internal : internals.getList()) {
- LauncherActivityInfo lai = new LauncherActivityInfo(mContext, user, internal);
+ LauncherActivityInfo lai = new LauncherActivityInfo(mContext, internal);
if (DEBUG) {
Log.v(TAG, "Returning activity for profile " + user + " : "
+ lai.getComponentName());
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9388823..6486278 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4353,19 +4353,26 @@
public static final String FEATURE_CREDENTIALS = "android.software.credentials";
/**
- * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
- * The device supports locking (for example, by a financing provider in case of a missed
- * payment).
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+ * supports locking (for example, by a financing provider in case of a missed payment).
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_DEVICE_LOCK = "android.software.device_lock";
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+ * supports showing location-based suggestions for wallet cards provided by the default payment
+ * app.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS =
+ "android.software.wallet_location_based_suggestions";
+
/** @hide */
public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
/**
- * Extra field name for the URI to a verification file. Passed to a package
- * verifier.
+ * Extra field name for the URI to a verification file. Passed to a package verifier.
*
* @hide
*/
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index f0230e7..0806f1d 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -26,12 +26,12 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentSender;
-import android.content.pm.ServiceInfo;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
@@ -75,21 +75,21 @@
*
* @hide
*/
- public static final int PROVIDER_FILTER_ALL_PROVIDERS = 0;
+ @TestApi public static final int PROVIDER_FILTER_ALL_PROVIDERS = 0;
/**
* Returns system credential providers only.
*
* @hide
*/
- public static final int PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY = 1;
+ @TestApi public static final int PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY = 1;
/**
* Returns user credential providers only.
*
* @hide
*/
- public static final int PROVIDER_FILTER_USER_PROVIDERS_ONLY = 2;
+ @TestApi public static final int PROVIDER_FILTER_USER_PROVIDERS_ONLY = 2;
private final Context mContext;
private final ICredentialManager mService;
@@ -263,44 +263,6 @@
}
/**
- * Gets a list of all user configurable credential providers registered on the system. This API
- * is intended for browsers and settings apps.
- *
- * @param cancellationSignal an optional signal that allows for cancelling this call
- * @param executor the callback will take place on this {@link Executor}
- * @param callback the callback invoked when the request succeeds or fails
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- public void listEnabledProviders(
- @Nullable CancellationSignal cancellationSignal,
- @CallbackExecutor @NonNull Executor executor,
- @NonNull
- OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException>
- callback) {
- requireNonNull(executor, "executor must not be null");
- requireNonNull(callback, "callback must not be null");
-
- if (cancellationSignal != null && cancellationSignal.isCanceled()) {
- Log.w(TAG, "listEnabledProviders already canceled");
- return;
- }
-
- ICancellationSignal cancelRemote = null;
- try {
- cancelRemote =
- mService.listEnabledProviders(
- new ListEnabledProvidersTransport(executor, callback));
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
-
- if (cancellationSignal != null && cancelRemote != null) {
- cancellationSignal.setRemote(cancelRemote);
- }
- }
-
- /**
* Sets a list of all user configurable credential providers registered on the system. This API
* is intended for settings apps.
*
@@ -348,36 +310,43 @@
}
/**
- * Returns the list of ServiceInfo for all discovered credential providers on this device.
+ * Returns the list of CredentialProviderInfo for all discovered credential providers on this
+ * device but will include test system providers as well.
*
* @hide
*/
@NonNull
- @RequiresPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS)
- public List<ServiceInfo> getCredentialProviderServicesForTesting(
- @ProviderFilter int providerFilter) {
+ @TestApi
+ @RequiresPermission(
+ anyOf = {
+ android.Manifest.permission.QUERY_ALL_PACKAGES,
+ android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS
+ })
+ public List<CredentialProviderInfo> getCredentialProviderServicesForTesting(
+ @ProviderFilter int providerFilter) {
try {
- return mService.getCredentialProviderServices(
- mContext.getUserId(),
- /* disableSystemAppVerificationForTests= */ true,
- providerFilter);
+ return mService.getCredentialProviderServicesForTesting(providerFilter);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Returns the list of ServiceInfo for all discovered credential providers on this device.
+ * Returns the list of CredentialProviderInfo for all discovered credential providers on this
+ * device.
*
* @hide
*/
@NonNull
- @RequiresPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS)
- public List<ServiceInfo> getCredentialProviderServices(
+ @RequiresPermission(
+ anyOf = {
+ android.Manifest.permission.QUERY_ALL_PACKAGES,
+ android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS
+ })
+ public List<CredentialProviderInfo> getCredentialProviderServices(
int userId, @ProviderFilter int providerFilter) {
try {
- return mService.getCredentialProviderServices(
- userId, /* disableSystemAppVerificationForTests= */ false, providerFilter);
+ return mService.getCredentialProviderServices(userId, providerFilter);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -388,7 +357,9 @@
*
* @hide
*/
- public static boolean isServiceEnabled(Context context) {
+ @TestApi
+ public static boolean isServiceEnabled(@NonNull Context context) {
+ requireNonNull(context, "context must not be null");
if (context == null) {
return false;
}
@@ -578,33 +549,6 @@
}
}
- private static class ListEnabledProvidersTransport extends IListEnabledProvidersCallback.Stub {
- // TODO: listen for cancellation to release callback.
-
- private final Executor mExecutor;
- private final OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException>
- mCallback;
-
- private ListEnabledProvidersTransport(
- Executor executor,
- OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException>
- callback) {
- mExecutor = executor;
- mCallback = callback;
- }
-
- @Override
- public void onResponse(ListEnabledProvidersResponse response) {
- mExecutor.execute(() -> mCallback.onResult(response));
- }
-
- @Override
- public void onError(String errorType, String message) {
- mExecutor.execute(
- () -> mCallback.onError(new ListEnabledProvidersException(errorType, message)));
- }
- }
-
private static class SetEnabledProvidersTransport extends ISetEnabledProvidersCallback.Stub {
// TODO: listen for cancellation to release callback.
diff --git a/core/java/android/credentials/IListEnabledProvidersCallback.aidl b/core/java/android/credentials/CredentialProviderInfo.aidl
similarity index 63%
rename from core/java/android/credentials/IListEnabledProvidersCallback.aidl
rename to core/java/android/credentials/CredentialProviderInfo.aidl
index 3a8e25ed..30b7742 100644
--- a/core/java/android/credentials/IListEnabledProvidersCallback.aidl
+++ b/core/java/android/credentials/CredentialProviderInfo.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 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.
@@ -16,14 +16,4 @@
package android.credentials;
-import android.credentials.ListEnabledProvidersResponse;
-
-/**
- * Listener for an listEnabledProviders request.
- *
- * @hide
- */
-interface IListEnabledProvidersCallback {
- oneway void onResponse(in ListEnabledProvidersResponse response);
- oneway void onError(String errorType, String message);
-}
\ No newline at end of file
+parcelable CredentialProviderInfo;
\ No newline at end of file
diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java
new file mode 100644
index 0000000..7276770
--- /dev/null
+++ b/core/java/android/credentials/CredentialProviderInfo.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2023 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.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * {@link ServiceInfo} and meta-data about a credential provider.
+ *
+ * @hide
+ */
+@TestApi
+public final class CredentialProviderInfo implements Parcelable {
+ @NonNull private final ServiceInfo mServiceInfo;
+ @NonNull private final List<String> mCapabilities = new ArrayList<>();
+ @Nullable private final CharSequence mOverrideLabel;
+ private final boolean mIsSystemProvider;
+ private final boolean mIsEnabled;
+
+ /**
+ * Constructs an information instance of the credential provider.
+ *
+ * @param builder the builder object.
+ */
+ private CredentialProviderInfo(@NonNull Builder builder) {
+ mServiceInfo = builder.mServiceInfo;
+ mCapabilities.addAll(builder.mCapabilities);
+ mIsSystemProvider = builder.mIsSystemProvider;
+ mIsEnabled = builder.mIsEnabled;
+ mOverrideLabel = builder.mOverrideLabel;
+ }
+
+ /** Returns true if the service supports the given {@code credentialType}, false otherwise. */
+ @NonNull
+ public boolean hasCapability(@NonNull String credentialType) {
+ return mCapabilities.contains(credentialType);
+ }
+
+ /** Returns the service info. */
+ @NonNull
+ public ServiceInfo getServiceInfo() {
+ return mServiceInfo;
+ }
+
+ /** Returns whether it is a system provider. */
+ public boolean isSystemProvider() {
+ return mIsSystemProvider;
+ }
+
+ /** Returns the service icon. */
+ @Nullable
+ public Drawable getServiceIcon(@NonNull Context context) {
+ return mServiceInfo.loadIcon(context.getPackageManager());
+ }
+
+ /** Returns the service label. */
+ @Nullable
+ public CharSequence getLabel(@NonNull Context context) {
+ if (mOverrideLabel != null) {
+ return mOverrideLabel;
+ }
+ return mServiceInfo.loadSafeLabel(context.getPackageManager());
+ }
+
+ /** Returns a list of capabilities this provider service can support. */
+ @NonNull
+ public List<String> getCapabilities() {
+ return Collections.unmodifiableList(mCapabilities);
+ }
+
+ /** Returns whether the provider is enabled by the user. */
+ public boolean isEnabled() {
+ return mIsEnabled;
+ }
+
+ /** Returns the component name for the service. */
+ @NonNull
+ public ComponentName getComponentName() {
+ return mServiceInfo.getComponentName();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedObject(mServiceInfo, flags);
+ dest.writeBoolean(mIsSystemProvider);
+ dest.writeStringList(mCapabilities);
+ dest.writeBoolean(mIsEnabled);
+ TextUtils.writeToParcel(mOverrideLabel, dest, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "CredentialProviderInfo {"
+ + "serviceInfo="
+ + mServiceInfo
+ + ", "
+ + "isSystemProvider="
+ + mIsSystemProvider
+ + ", "
+ + "isEnabled="
+ + mIsEnabled
+ + ", "
+ + "overrideLabel="
+ + mOverrideLabel
+ + ", "
+ + "capabilities="
+ + String.join(",", mCapabilities)
+ + "}";
+ }
+
+ private CredentialProviderInfo(@NonNull Parcel in) {
+ mServiceInfo = in.readTypedObject(ServiceInfo.CREATOR);
+ mIsSystemProvider = in.readBoolean();
+ in.readStringList(mCapabilities);
+ mIsEnabled = in.readBoolean();
+ mOverrideLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ }
+
+ public static final @NonNull Parcelable.Creator<CredentialProviderInfo> CREATOR =
+ new Parcelable.Creator<CredentialProviderInfo>() {
+ @Override
+ public CredentialProviderInfo[] newArray(int size) {
+ return new CredentialProviderInfo[size];
+ }
+
+ @Override
+ public CredentialProviderInfo createFromParcel(@NonNull Parcel in) {
+ return new CredentialProviderInfo(in);
+ }
+ };
+
+ /** A builder for {@link CredentialProviderInfo} objects. */
+ public static final class Builder {
+
+ @NonNull private ServiceInfo mServiceInfo;
+ @NonNull private List<String> mCapabilities = new ArrayList<>();
+ private boolean mIsSystemProvider = false;
+ private boolean mIsEnabled = false;
+ @Nullable private CharSequence mOverrideLabel = null;
+
+ /**
+ * Creates a new builder.
+ *
+ * @param serviceInfo the service info of the credential provider service.
+ */
+ public Builder(@NonNull ServiceInfo serviceInfo) {
+ mServiceInfo = serviceInfo;
+ }
+
+ /** Sets whether it is a system provider. */
+ public @NonNull Builder setSystemProvider(boolean isSystemProvider) {
+ mIsSystemProvider = isSystemProvider;
+ return this;
+ }
+
+ /**
+ * Sets the label to be used instead of getting from the system (for unit tests).
+ *
+ * @hide
+ */
+ public @NonNull Builder setOverrideLabel(@NonNull CharSequence overrideLabel) {
+ mOverrideLabel = overrideLabel;
+ return this;
+ }
+
+ /** Sets a list of capabilities this provider service can support. */
+ public @NonNull Builder addCapabilities(@NonNull List<String> capabilities) {
+ mCapabilities.addAll(capabilities);
+ return this;
+ }
+
+ /** Sets whether it is enabled by the user. */
+ public @NonNull Builder setEnabled(boolean isEnabled) {
+ mIsEnabled = isEnabled;
+ return this;
+ }
+
+ /** Builds a new {@link CredentialProviderInfo} instance. */
+ public @NonNull CredentialProviderInfo build() {
+ return new CredentialProviderInfo(this);
+ }
+ }
+}
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index 625fc8a..8c2cb5a 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -18,7 +18,7 @@
import java.util.List;
-import android.content.pm.ServiceInfo;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
@@ -27,7 +27,6 @@
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.IGetCredentialCallback;
-import android.credentials.IListEnabledProvidersCallback;
import android.credentials.ISetEnabledProvidersCallback;
import android.content.ComponentName;
import android.os.ICancellationSignal;
@@ -45,8 +44,6 @@
@nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
- @nullable ICancellationSignal listEnabledProviders(in IListEnabledProvidersCallback callback);
-
void setEnabledProviders(in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback);
void registerCredentialDescription(in RegisterCredentialDescriptionRequest request, String callingPackage);
@@ -55,6 +52,8 @@
boolean isEnabledCredentialProviderService(in ComponentName componentName, String callingPackage);
- List<ServiceInfo> getCredentialProviderServices(in int userId, in boolean disableSystemAppVerificationForTests, in int providerFilter);
+ List<CredentialProviderInfo> getCredentialProviderServices(in int userId, in int providerFilter);
+
+ List<CredentialProviderInfo> getCredentialProviderServicesForTesting(in int providerFilter);
}
diff --git a/core/java/android/credentials/ui/Entry.java b/core/java/android/credentials/ui/Entry.java
index 12665ba..55f2a3e 100644
--- a/core/java/android/credentials/ui/Entry.java
+++ b/core/java/android/credentials/ui/Entry.java
@@ -70,17 +70,6 @@
/** Constructor to be used for an entry that requires a pending intent to be invoked
* when clicked.
*/
- // TODO: Remove this constructor as it is no longer used
- public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
- @NonNull PendingIntent pendingIntent, @NonNull Intent intent) {
- this(key, subkey, slice);
- mPendingIntent = pendingIntent;
- mFrameworkExtrasIntent = intent;
- }
-
- /** Constructor to be used for an entry that requires a pending intent to be invoked
- * when clicked.
- */
public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
@NonNull Intent intent) {
this(key, subkey, slice);
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 4b3eb3a..4532661 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -1392,6 +1392,10 @@
return sql.replaceAll("[\\s]*\\n+[\\s]*", " ");
}
+ void clearPreparedStatementCache() {
+ mPreparedStatementCache.evictAll();
+ }
+
/**
* Holder type for a prepared statement.
*
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index 069c264..6023d66 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -1126,6 +1126,16 @@
mConnectionWaiterPool = waiter;
}
+ void clearAcquiredConnectionsPreparedStatementCache() {
+ synchronized (mLock) {
+ if (!mAcquiredConnections.isEmpty()) {
+ for (SQLiteConnection connection : mAcquiredConnections.keySet()) {
+ connection.clearPreparedStatementCache();
+ }
+ }
+ }
+ }
+
/**
* Dumps debugging information about this connection pool.
*
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index c08294f..db898c3 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -2088,10 +2088,12 @@
try (SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs)) {
return statement.executeUpdateDelete();
} finally {
- // If schema was updated, close non-primary connections, otherwise they might
- // have outdated schema information
+ // If schema was updated, close non-primary connections and clear prepared
+ // statement caches of active connections, otherwise they might have outdated
+ // schema information.
if (statementType == DatabaseUtils.STATEMENT_DDL) {
mConnectionPoolLocked.closeAvailableNonPrimaryConnectionsAndLogExceptions();
+ mConnectionPoolLocked.clearAcquiredConnectionsPreparedStatementCache();
}
}
} finally {
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 9640b0e..631df01 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1132,6 +1132,12 @@
private boolean setGpsLocation(Location l) {
if (l == null) {
+ // If Location value being set is null, remove corresponding keys.
+ // This is safe because api1/client2/CameraParameters.cpp already erases
+ // the keys for JPEG_GPS_LOCATION for certain cases.
+ setBase(CaptureRequest.JPEG_GPS_TIMESTAMP, null);
+ setBase(CaptureRequest.JPEG_GPS_COORDINATES, null);
+ setBase(CaptureRequest.JPEG_GPS_PROCESSING_METHOD, null);
return false;
}
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index 067ae4d..490e55b 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -28,6 +28,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
+import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.Surface;
@@ -53,6 +54,8 @@
private final int mDisplayIdToMirror;
private final boolean mWindowManagerMirroringEnabled;
private ArraySet<String> mDisplayCategories = null;
+ @Nullable
+ private ContentRecordingSession mContentRecordingSession;
private final float mRequestedRefreshRate;
private VirtualDisplayConfig(
@@ -65,6 +68,7 @@
@Nullable String uniqueId,
int displayIdToMirror,
boolean windowManagerMirroringEnabled,
+ ContentRecordingSession session,
@NonNull ArraySet<String> displayCategories,
float requestedRefreshRate) {
mName = name;
@@ -76,6 +80,7 @@
mUniqueId = uniqueId;
mDisplayIdToMirror = displayIdToMirror;
mWindowManagerMirroringEnabled = windowManagerMirroringEnabled;
+ mContentRecordingSession = session;
mDisplayCategories = displayCategories;
mRequestedRefreshRate = requestedRefreshRate;
}
@@ -156,6 +161,17 @@
}
/**
+ * Returns the recording session associated with this VirtualDisplay. Only used for
+ * recording via {@link MediaProjection}.
+ *
+ * @hide
+ */
+ @Nullable
+ public ContentRecordingSession getContentRecordingSession() {
+ return mContentRecordingSession;
+ }
+
+ /**
* Returns the display categories.
*
* @see Builder#setDisplayCategories
@@ -186,6 +202,7 @@
dest.writeString8(mUniqueId);
dest.writeInt(mDisplayIdToMirror);
dest.writeBoolean(mWindowManagerMirroringEnabled);
+ dest.writeTypedObject(mContentRecordingSession, flags);
dest.writeArraySet(mDisplayCategories);
dest.writeFloat(mRequestedRefreshRate);
}
@@ -211,6 +228,7 @@
&& Objects.equals(mUniqueId, that.mUniqueId)
&& mDisplayIdToMirror == that.mDisplayIdToMirror
&& mWindowManagerMirroringEnabled == that.mWindowManagerMirroringEnabled
+ && Objects.equals(mContentRecordingSession, that.mContentRecordingSession)
&& Objects.equals(mDisplayCategories, that.mDisplayCategories)
&& mRequestedRefreshRate == that.mRequestedRefreshRate;
}
@@ -219,8 +237,8 @@
public int hashCode() {
int hashCode = Objects.hash(
mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId,
- mDisplayIdToMirror, mWindowManagerMirroringEnabled, mDisplayCategories,
- mRequestedRefreshRate);
+ mDisplayIdToMirror, mWindowManagerMirroringEnabled, mContentRecordingSession,
+ mDisplayCategories, mRequestedRefreshRate);
return hashCode;
}
@@ -237,6 +255,7 @@
+ " mUniqueId=" + mUniqueId
+ " mDisplayIdToMirror=" + mDisplayIdToMirror
+ " mWindowManagerMirroringEnabled=" + mWindowManagerMirroringEnabled
+ + " mContentRecordingSession=" + mContentRecordingSession
+ " mDisplayCategories=" + mDisplayCategories
+ " mRequestedRefreshRate=" + mRequestedRefreshRate
+ ")";
@@ -252,6 +271,7 @@
mUniqueId = in.readString8();
mDisplayIdToMirror = in.readInt();
mWindowManagerMirroringEnabled = in.readBoolean();
+ mContentRecordingSession = in.readTypedObject(ContentRecordingSession.CREATOR);
mDisplayCategories = (ArraySet<String>) in.readArraySet(null);
mRequestedRefreshRate = in.readFloat();
}
@@ -283,6 +303,8 @@
private String mUniqueId = null;
private int mDisplayIdToMirror = DEFAULT_DISPLAY;
private boolean mWindowManagerMirroringEnabled = false;
+ @Nullable
+ private ContentRecordingSession mContentRecordingSession;
private ArraySet<String> mDisplayCategories = new ArraySet<>();
private float mRequestedRefreshRate = 0.0f;
@@ -375,6 +397,18 @@
}
/**
+ * Sets the recording session associated with this {@link VirtualDisplay}. Only used for
+ * recording via {@link MediaProjection}.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setContentRecordingSession(@Nullable ContentRecordingSession session) {
+ mContentRecordingSession = session;
+ return this;
+ }
+
+ /**
* Sets the display categories.
*
* <p>The categories of the display indicate the type of activities allowed to run on that
@@ -435,6 +469,7 @@
mUniqueId,
mDisplayIdToMirror,
mWindowManagerMirroringEnabled,
+ mContentRecordingSession,
mDisplayCategories,
mRequestedRefreshRate);
}
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index fb8f84a..a52e3d49 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -474,9 +474,8 @@
* target functionality. The ANGLE broadcast receiver code will apply a "deferlist" at
* the first boot of a newly-flashed device. However, there is a gap in time between
* when applications can start and when the deferlist is applied. For now, assume that
- * if ANGLE is the system driver and Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS is
- * empty, that the deferlist has not yet been applied. In this case, select the Legacy
- * driver.
+ * if ANGLE is the system driver and Settings.Global.ANGLE_DEFERLIST is empty, that the
+ * deferlist has not yet been applied. In this case, select the Legacy driver.
* otherwise ...
* 3) Use ANGLE if isAngleEnabledByGameMode() returns true; otherwise ...
* 4) The global switch (i.e. use the system driver, whether ANGLE or legacy;
@@ -516,14 +515,17 @@
contentResolver, bundle, Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS);
final List<String> optInValues = getGlobalSettingsString(
contentResolver, bundle, Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES);
+ final List<String> angleDeferlist = getGlobalSettingsString(
+ contentResolver, bundle, Settings.Global.ANGLE_DEFERLIST);
Log.v(TAG, "Currently set values for:");
Log.v(TAG, " angle_gl_driver_selection_pkgs =" + optInPackages);
Log.v(TAG, " angle_gl_driver_selection_values =" + optInValues);
// If ANGLE is the system driver AND the deferlist has not yet been applied, select the
// Legacy driver
- if (mAngleIsSystemDriver && optInPackages.size() <= 1) {
- Log.v(TAG, "Ignoring angle_gl_driver_selection_* until deferlist has been applied");
+ if (mAngleIsSystemDriver && angleDeferlist.size() == 0) {
+ Log.v(TAG, "ANGLE deferlist (" + Settings.Global.ANGLE_DEFERLIST + ") has not been "
+ + "applied, defaulting to legacy driver");
return ANGLE_GL_DRIVER_TO_USE_LEGACY;
}
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 07d5001..5b527c7 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -979,6 +979,19 @@
}
/**
+ * An unrestricted version of getType, which does not reveal sensitive information
+ */
+ @Override
+ public final @Nullable String getTypeAnonymous(@NonNull Uri uri) {
+ switch (mMatcher.match(uri)) {
+ case MATCH_ROOT:
+ return DocumentsContract.Root.MIME_TYPE_ITEM;
+ default:
+ return null;
+ }
+ }
+
+ /**
* Implementation is provided by the parent class. Can be overridden to
* provide additional functionality, but subclasses <em>must</em> always
* call the superclass. If the superclass returns {@code null}, the subclass
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 0ef8bb64..e81ca1a 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -28,6 +28,7 @@
import android.content.IntentSender;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.ArrayMap;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
@@ -434,14 +435,14 @@
* one value for a field or set an authentication intent.
*/
public static final class Builder {
- private ArrayList<AutofillId> mFieldIds;
- private ArrayList<AutofillValue> mFieldValues;
- private ArrayList<RemoteViews> mFieldPresentations;
- private ArrayList<RemoteViews> mFieldDialogPresentations;
- private ArrayList<InlinePresentation> mFieldInlinePresentations;
- private ArrayList<InlinePresentation> mFieldInlineTooltipPresentations;
- private ArrayList<DatasetFieldFilter> mFieldFilters;
- private ArrayList<String> mAutofillDatatypes;
+ private ArrayList<AutofillId> mFieldIds = new ArrayList<>();
+ private ArrayList<AutofillValue> mFieldValues = new ArrayList();
+ private ArrayList<RemoteViews> mFieldPresentations = new ArrayList();
+ private ArrayList<RemoteViews> mFieldDialogPresentations = new ArrayList();
+ private ArrayList<InlinePresentation> mFieldInlinePresentations = new ArrayList();
+ private ArrayList<InlinePresentation> mFieldInlineTooltipPresentations = new ArrayList();
+ private ArrayList<DatasetFieldFilter> mFieldFilters = new ArrayList();
+ private ArrayList<String> mAutofillDatatypes = new ArrayList();
@Nullable private ClipData mFieldContent;
private RemoteViews mPresentation;
private RemoteViews mDialogPresentation;
@@ -452,6 +453,15 @@
@Nullable private String mId;
/**
+ * Usually, a field will be associated with a single autofill id and/or datatype.
+ * There could be null field value corresponding to different autofill ids or datatye
+ * values, but the implementation is ok with duplicating that information.
+ * This map is just for the purpose of optimization, to reduce the size of the pelled data
+ * over the binder transaction.
+ */
+ private ArrayMap<Field, Integer> mFieldToIndexdMap = new ArrayMap<>();
+
+ /**
* Creates a new builder.
*
* @param presentation The presentation used to visualize this dataset.
@@ -1051,29 +1061,40 @@
*/
public @NonNull Builder setField(@NonNull AutofillId id, @Nullable Field field) {
throwIfDestroyed();
+
+ if (mFieldToIndexdMap.containsKey(field)) {
+ int index = mFieldToIndexdMap.get(field);
+ if (mFieldIds.get(index) == null) {
+ mFieldIds.set(index, id);
+ return this;
+ }
+ // if the Autofill Id is already set, ignore and proceed as if setting in a new
+ // value.
+ }
+ int index;
if (field == null) {
- setLifeTheUniverseAndEverything(id, null, null, null, null, null, null);
+ index = setLifeTheUniverseAndEverything(id, null, null, null, null, null, null);
} else {
final DatasetFieldFilter filter = field.getDatasetFieldFilter();
final Presentations presentations = field.getPresentations();
if (presentations == null) {
- setLifeTheUniverseAndEverything(id, field.getValue(), null, null, null,
+ index = setLifeTheUniverseAndEverything(id, field.getValue(), null, null, null,
filter, null);
} else {
- setLifeTheUniverseAndEverything(id, field.getValue(),
+ index = setLifeTheUniverseAndEverything(id, field.getValue(),
presentations.getMenuPresentation(),
presentations.getInlinePresentation(),
presentations.getInlineTooltipPresentation(), filter,
presentations.getDialogPresentation());
}
}
+ mFieldToIndexdMap.put(field, index);
return this;
}
/**
- * Adds a field to this Dataset with a specific type and no
- * AutofillId. This is used to send back Field information
- * when Autofilling with platform detections is on.
+ * Adds a field to this Dataset with a specific type. This is used to send back Field
+ * information when Autofilling with platform detections is on.
* Platform detections are on when receiving a populated list from
* FillRequest#getHints().
*
@@ -1086,9 +1107,6 @@
* has two credential pairs, then two Datasets should be created,
* and so on.
*
- * Using this will remove any data populated with
- * setField(@NonNull AutofillId id, @Nullable Field field).
- *
* @param hint An autofill hint returned from {@link
* FillRequest#getHints()}.
*
@@ -1102,19 +1120,29 @@
public @NonNull Dataset.Builder setField(@NonNull String hint, @NonNull Field field) {
throwIfDestroyed();
+ if (mFieldToIndexdMap.containsKey(field)) {
+ int index = mFieldToIndexdMap.get(field);
+ if (mAutofillDatatypes.get(index) == null) {
+ mAutofillDatatypes.set(index, hint);
+ return this;
+ }
+ // if the hint is already set, ignore and proceed as if setting in a new hint.
+ }
+
+ int index;
final DatasetFieldFilter filter = field.getDatasetFieldFilter();
final Presentations presentations = field.getPresentations();
if (presentations == null) {
- setLifeTheUniverseAndEverything(hint, field.getValue(), null, null, null,
+ index = setLifeTheUniverseAndEverything(hint, field.getValue(), null, null, null,
filter, null);
} else {
- setLifeTheUniverseAndEverything(hint, field.getValue(),
+ index = setLifeTheUniverseAndEverything(hint, field.getValue(),
presentations.getMenuPresentation(),
presentations.getInlinePresentation(),
presentations.getInlineTooltipPresentation(), filter,
presentations.getDialogPresentation());
}
-
+ mFieldToIndexdMap.put(field, index);
return this;
}
@@ -1172,67 +1200,64 @@
return this;
}
- private void setLifeTheUniverseAndEverything(String datatype,
+ /** Returns the index at which this id was modified or inserted */
+ private int setLifeTheUniverseAndEverything(@NonNull String datatype,
@Nullable AutofillValue value,
@Nullable RemoteViews presentation,
@Nullable InlinePresentation inlinePresentation,
@Nullable InlinePresentation tooltip,
@Nullable DatasetFieldFilter filter,
@Nullable RemoteViews dialogPresentation) {
- if (mAutofillDatatypes == null) {
- mFieldValues = new ArrayList<>();
- mFieldPresentations = new ArrayList<>();
- mFieldDialogPresentations = new ArrayList<>();
- mFieldInlinePresentations = new ArrayList<>();
- mFieldInlineTooltipPresentations = new ArrayList<>();
- mFieldFilters = new ArrayList<>();
- mAutofillDatatypes = new ArrayList<>();
- mFieldIds = null;
+ Objects.requireNonNull(datatype, "datatype cannot be null");
+ final int existingIdx = mAutofillDatatypes.indexOf(datatype);
+ if (existingIdx >= 0) {
+ mAutofillDatatypes.add(datatype);
+ mFieldValues.set(existingIdx, value);
+ mFieldPresentations.set(existingIdx, presentation);
+ mFieldDialogPresentations.set(existingIdx, dialogPresentation);
+ mFieldInlinePresentations.set(existingIdx, inlinePresentation);
+ mFieldInlineTooltipPresentations.set(existingIdx, tooltip);
+ mFieldFilters.set(existingIdx, filter);
+ return existingIdx;
}
+ mFieldIds.add(null);
+ mAutofillDatatypes.add(datatype);
mFieldValues.add(value);
mFieldPresentations.add(presentation);
mFieldDialogPresentations.add(dialogPresentation);
mFieldInlinePresentations.add(inlinePresentation);
mFieldInlineTooltipPresentations.add(tooltip);
mFieldFilters.add(filter);
- mAutofillDatatypes.add(datatype);
+ return mFieldIds.size() - 1;
}
- private void setLifeTheUniverseAndEverything(@NonNull AutofillId id,
+ /** Returns the index at which this id was modified or inserted */
+ private int setLifeTheUniverseAndEverything(@NonNull AutofillId id,
@Nullable AutofillValue value, @Nullable RemoteViews presentation,
@Nullable InlinePresentation inlinePresentation,
@Nullable InlinePresentation tooltip,
@Nullable DatasetFieldFilter filter,
@Nullable RemoteViews dialogPresentation) {
Objects.requireNonNull(id, "id cannot be null");
- if (mFieldIds != null) {
- final int existingIdx = mFieldIds.indexOf(id);
- if (existingIdx >= 0) {
- mFieldValues.set(existingIdx, value);
- mFieldPresentations.set(existingIdx, presentation);
- mFieldDialogPresentations.set(existingIdx, dialogPresentation);
- mFieldInlinePresentations.set(existingIdx, inlinePresentation);
- mFieldInlineTooltipPresentations.set(existingIdx, tooltip);
- mFieldFilters.set(existingIdx, filter);
- return;
- }
- } else {
- mFieldIds = new ArrayList<>();
- mFieldValues = new ArrayList<>();
- mFieldPresentations = new ArrayList<>();
- mFieldDialogPresentations = new ArrayList<>();
- mFieldInlinePresentations = new ArrayList<>();
- mFieldInlineTooltipPresentations = new ArrayList<>();
- mFieldFilters = new ArrayList<>();
- mAutofillDatatypes = null;
+ final int existingIdx = mFieldIds.indexOf(id);
+ if (existingIdx >= 0) {
+ mFieldValues.set(existingIdx, value);
+ mFieldPresentations.set(existingIdx, presentation);
+ mFieldDialogPresentations.set(existingIdx, dialogPresentation);
+ mFieldInlinePresentations.set(existingIdx, inlinePresentation);
+ mFieldInlineTooltipPresentations.set(existingIdx, tooltip);
+ mFieldFilters.set(existingIdx, filter);
+ return existingIdx;
}
mFieldIds.add(id);
+ mAutofillDatatypes.add(null);
mFieldValues.add(value);
mFieldPresentations.add(presentation);
mFieldDialogPresentations.add(dialogPresentation);
mFieldInlinePresentations.add(inlinePresentation);
mFieldInlineTooltipPresentations.add(tooltip);
mFieldFilters.add(filter);
+ return mFieldIds.size() - 1;
}
/**
@@ -1249,11 +1274,12 @@
throwIfDestroyed();
mDestroyed = true;
if (mFieldIds == null && mAutofillDatatypes == null) {
- throw new IllegalStateException("at least one value must be set");
+ throw new IllegalStateException("at least one of field or datatype must be set");
}
if (mFieldIds != null && mAutofillDatatypes != null) {
- if (mFieldIds.size() > 0 && mAutofillDatatypes.size() > 0) {
- throw new IllegalStateException("both field and datatype were populated");
+ if (mFieldIds.size() == 0 && mAutofillDatatypes.size() == 0) {
+ throw new IllegalStateException(
+ "at least one of field or datatype must be set");
}
}
if (mFieldContent != null) {
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index 85eac6e..e9cebd2 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -131,9 +131,7 @@
* @param slice the slice containing the metadata to be shown on the UI. Must be
* constructed through the androidx.credentials jetpack library.
*
- * @hide
*/
- // TODO: Unhide this constructor when the registry APIs are stable
public CredentialEntry(@NonNull String type, @NonNull Slice slice) {
mBeginGetCredentialOptionId = null;
mType = requireNonNull(type, "type must not be null");
diff --git a/core/java/android/service/credentials/CredentialProviderInfo.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
similarity index 63%
rename from core/java/android/service/credentials/CredentialProviderInfo.java
rename to core/java/android/service/credentials/CredentialProviderInfoFactory.java
index b5464db..9cd5aa0 100644
--- a/core/java/android/service/credentials/CredentialProviderInfo.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -34,40 +34,29 @@
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.credentials.CredentialManager;
-import android.graphics.drawable.Drawable;
+import android.credentials.CredentialProviderInfo;
import android.os.Bundle;
import android.os.RemoteException;
-import android.text.TextUtils;
+import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
- * {@link ServiceInfo} and meta-data about a credential provider.
+ * {@link CredentialProviderInfo} generator.
*
* @hide
*/
-public final class CredentialProviderInfo {
- private static final String TAG = "CredentialProviderInfo";
-
- @NonNull
- private final ServiceInfo mServiceInfo;
- @NonNull
- private final List<String> mCapabilities;
-
- @NonNull
- private final Context mContext;
- @Nullable
- private final Drawable mIcon;
- @Nullable
- private final CharSequence mLabel;
- private final boolean mIsSystemProvider;
+public final class CredentialProviderInfoFactory {
+ private static final String TAG = "CredentialProviderInfoFactory";
/**
* Constructs an information instance of the credential provider.
@@ -79,14 +68,18 @@
* @throws PackageManager.NameNotFoundException If provider service is not found
* @throws SecurityException If provider does not require the relevant permission
*/
- public CredentialProviderInfo(@NonNull Context context,
- @NonNull ComponentName serviceComponent, int userId, boolean isSystemProvider)
+ public static CredentialProviderInfo create(
+ @NonNull Context context,
+ @NonNull ComponentName serviceComponent,
+ int userId,
+ boolean isSystemProvider)
throws PackageManager.NameNotFoundException {
- this(
+ return create(
context,
getServiceInfoOrThrow(serviceComponent, userId),
isSystemProvider,
- /* disableSystemAppVerificationForTests= */ false);
+ /* disableSystemAppVerificationForTests= */ false,
+ /* isEnabled= */ false);
}
/**
@@ -98,13 +91,16 @@
* @param isSystemProvider whether the provider app is a system provider
* @param disableSystemAppVerificationForTests whether to disable system app permission
* verification so that tests can install system providers
+ * @param isEnabled whether the user enabled this provider
* @throws SecurityException If provider does not require the relevant permission
*/
- public CredentialProviderInfo(
+ public static CredentialProviderInfo create(
@NonNull Context context,
@NonNull ServiceInfo serviceInfo,
boolean isSystemProvider,
- boolean disableSystemAppVerificationForTests) {
+ boolean disableSystemAppVerificationForTests,
+ boolean isEnabled)
+ throws SecurityException {
verifyProviderPermission(serviceInfo);
if (isSystemProvider) {
if (!isValidSystemProvider(
@@ -114,23 +110,31 @@
"Provider is not a valid system provider: " + serviceInfo);
}
}
- mIsSystemProvider = isSystemProvider;
- mContext = requireNonNull(context, "context must not be null");
- mServiceInfo = requireNonNull(serviceInfo, "serviceInfo must not be null");
- mCapabilities = new ArrayList<>();
- mIcon = mServiceInfo.loadIcon(mContext.getPackageManager());
- mLabel =
- mServiceInfo.loadSafeLabel(
- mContext.getPackageManager(),
- 0 /* do not ellipsize */,
- TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
- Log.i(
- TAG,
- "mLabel is : "
- + mLabel
- + ", for: "
- + mServiceInfo.getComponentName().flattenToString());
- populateProviderCapabilities(context, serviceInfo);
+
+ return populateMetadata(context, serviceInfo)
+ .setSystemProvider(isSystemProvider)
+ .setEnabled(isEnabled)
+ .build();
+ }
+
+ /**
+ * Constructs an information instance of the credential provider for testing purposes. Does
+ * not run any verifications and passes parameters as is.
+ */
+ @VisibleForTesting
+ public static CredentialProviderInfo createForTests(
+ @NonNull ServiceInfo serviceInfo,
+ @NonNull CharSequence overrideLabel,
+ boolean isSystemProvider,
+ boolean isEnabled,
+ @NonNull List<String> capabilities) {
+ return new CredentialProviderInfo.Builder(serviceInfo)
+ .setEnabled(isEnabled)
+ .setOverrideLabel(overrideLabel)
+ .setSystemProvider(isSystemProvider)
+ .addCapabilities(capabilities)
+ .build();
+
}
private static void verifyProviderPermission(ServiceInfo serviceInfo) throws SecurityException {
@@ -138,19 +142,14 @@
if (permission.equals(serviceInfo.permission)) {
return;
}
-
- Slog.e(
- TAG,
- "Credential Provider Service from : "
- + serviceInfo.packageName
- + "does not require permission"
- + permission);
throw new SecurityException(
"Service does not require the expected permission : " + permission);
}
private static boolean isSystemProviderWithValidPermission(
ServiceInfo serviceInfo, Context context) {
+ requireNonNull(context, "context must not be null");
+
final String permission = Manifest.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE;
try {
ApplicationInfo appInfo =
@@ -177,67 +176,88 @@
Context context,
ServiceInfo serviceInfo,
boolean disableSystemAppVerificationForTests) {
- boolean isValidSystemTestProvider =
- isTestSystemProvider(serviceInfo, disableSystemAppVerificationForTests);
- if (isValidSystemTestProvider) {
- return true;
+ requireNonNull(context, "context must not be null");
+
+ if (disableSystemAppVerificationForTests) {
+ Bundle metadata = serviceInfo.metaData;
+ if (metadata == null) {
+ Slog.e(TAG, "isValidSystemProvider - metadata is null: " + serviceInfo);
+ return false;
+ }
+ return metadata.getBoolean(
+ CredentialProviderService.TEST_SYSTEM_PROVIDER_META_DATA_KEY);
}
+
return isSystemProviderWithValidPermission(serviceInfo, context);
}
- private static boolean isTestSystemProvider(
- ServiceInfo serviceInfo, boolean disableSystemAppVerificationForTests) {
- if (!disableSystemAppVerificationForTests) {
- return false;
- }
+ private static CredentialProviderInfo.Builder populateMetadata(
+ @NonNull Context context, ServiceInfo serviceInfo) {
+ requireNonNull(context, "context must not be null");
- Bundle metadata = serviceInfo.metaData;
- if (metadata == null) {
- Slog.e(TAG, "metadata is null: " + serviceInfo);
- return false;
- }
- return metadata.getBoolean(CredentialProviderService.TEST_SYSTEM_PROVIDER_META_DATA_KEY);
- }
-
- private void populateProviderCapabilities(@NonNull Context context, ServiceInfo serviceInfo) {
+ final CredentialProviderInfo.Builder builder =
+ new CredentialProviderInfo.Builder(serviceInfo);
final PackageManager pm = context.getPackageManager();
+
+ // 1. Get the metadata for the service.
+ final Bundle metadata = serviceInfo.metaData;
+ if (metadata == null) {
+ Log.i(TAG, "populateMetadata - metadata is null");
+ return builder;
+ }
+
+ // 2. Extract the capabilities from the bundle.
try {
- Bundle metadata = serviceInfo.metaData;
Resources resources = pm.getResourcesForApplication(serviceInfo.applicationInfo);
if (metadata == null || resources == null) {
- Log.i(TAG, "populateProviderCapabilities - metadata or resources is null");
- return;
+ Log.i(TAG, "populateMetadata - resources is null");
+ return builder;
}
- String[] capabilities = resources.getStringArray(metadata.getInt(
- CredentialProviderService.CAPABILITY_META_DATA_KEY));
- if (capabilities == null || capabilities.length == 0) {
- Slog.i(TAG, "No capabilities found for provider:" + serviceInfo.packageName);
- return;
- }
-
- for (String capability : capabilities) {
- if (capability.isEmpty()) {
- Slog.i(TAG, "Skipping empty capability");
- continue;
- }
- Slog.i(TAG, "Capabilities found for provider: " + capability);
- mCapabilities.add(capability);
- }
+ builder.addCapabilities(populateProviderCapabilities(resources, metadata, serviceInfo));
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, e.getMessage());
- } catch (Resources.NotFoundException e) {
- Slog.e(TAG, e.getMessage());
}
+
+ return builder;
}
- private static ServiceInfo getServiceInfoOrThrow(@NonNull ComponentName serviceComponent,
- int userId) throws PackageManager.NameNotFoundException {
+ private static List<String> populateProviderCapabilities(
+ Resources resources, Bundle metadata, ServiceInfo serviceInfo) {
+ List<String> output = new ArrayList<>();
+ String[] capabilities = new String[0];
+
try {
- ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(
- serviceComponent,
- PackageManager.GET_META_DATA,
- userId);
+ capabilities =
+ resources.getStringArray(
+ metadata.getInt(CredentialProviderService.CAPABILITY_META_DATA_KEY));
+ } catch (Resources.NotFoundException e) {
+ Slog.e(TAG, "Failed to get capabilities: " + e.getMessage());
+ }
+
+ if (capabilities == null || capabilities.length == 0) {
+ Slog.e(TAG, "No capabilities found for provider:" + serviceInfo.packageName);
+ return output;
+ }
+
+ for (String capability : capabilities) {
+ if (capability.isEmpty()) {
+ Slog.e(TAG, "Skipping empty capability");
+ continue;
+ }
+ Slog.e(TAG, "Capabilities found for provider: " + capability);
+ output.add(capability);
+ }
+ return output;
+ }
+
+ private static ServiceInfo getServiceInfoOrThrow(
+ @NonNull ComponentName serviceComponent, int userId)
+ throws PackageManager.NameNotFoundException {
+ try {
+ ServiceInfo si =
+ AppGlobals.getPackageManager()
+ .getServiceInfo(serviceComponent, PackageManager.GET_META_DATA, userId);
if (si != null) {
return si;
}
@@ -256,6 +276,8 @@
@NonNull Context context,
@UserIdInt int userId,
boolean disableSystemAppVerificationForTests) {
+ requireNonNull(context, "context must not be null");
+
final List<ServiceInfo> services = new ArrayList<>();
final List<ResolveInfo> resolveInfos = new ArrayList<>();
@@ -268,15 +290,20 @@
for (ResolveInfo resolveInfo : resolveInfos) {
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ if (disableSystemAppVerificationForTests) {
+ if (serviceInfo != null) {
+ services.add(serviceInfo);
+ }
+ continue;
+ }
+
try {
- PackageManager.ApplicationInfoFlags appInfoFlags =
- disableSystemAppVerificationForTests
- ? PackageManager.ApplicationInfoFlags.of(0)
- : PackageManager.ApplicationInfoFlags.of(
- PackageManager.MATCH_SYSTEM_ONLY);
ApplicationInfo appInfo =
context.getPackageManager()
- .getApplicationInfo(serviceInfo.packageName, appInfoFlags);
+ .getApplicationInfo(
+ serviceInfo.packageName,
+ PackageManager.ApplicationInfoFlags.of(
+ PackageManager.MATCH_SYSTEM_ONLY));
if (appInfo == null || serviceInfo == null) {
continue;
@@ -300,19 +327,22 @@
public static List<CredentialProviderInfo> getAvailableSystemServices(
@NonNull Context context,
@UserIdInt int userId,
- boolean disableSystemAppVerificationForTests) {
+ boolean disableSystemAppVerificationForTests,
+ Set<ServiceInfo> enabledServices) {
requireNonNull(context, "context must not be null");
+
final List<CredentialProviderInfo> providerInfos = new ArrayList<>();
for (ServiceInfo si :
getAvailableSystemServiceInfos(
context, userId, disableSystemAppVerificationForTests)) {
try {
CredentialProviderInfo cpi =
- new CredentialProviderInfo(
+ CredentialProviderInfoFactory.create(
context,
si,
/* isSystemProvider= */ true,
- disableSystemAppVerificationForTests);
+ disableSystemAppVerificationForTests,
+ enabledServices.contains(si));
if (cpi.isSystemProvider()) {
providerInfos.add(cpi);
} else {
@@ -325,45 +355,12 @@
return providerInfos;
}
- /**
- * Returns true if the service supports the given {@code credentialType}, false otherwise.
- */
- @NonNull
- public boolean hasCapability(@NonNull String credentialType) {
- return mCapabilities.contains(credentialType);
- }
+ private static @Nullable PackagePolicy getDeviceManagerPolicy(
+ @NonNull Context context, int userId) {
+ Context newContext = context.createContextAsUser(UserHandle.of(userId), 0);
- /** Returns the service info. */
- @NonNull
- public ServiceInfo getServiceInfo() {
- return mServiceInfo;
- }
-
- public boolean isSystemProvider() {
- return mIsSystemProvider;
- }
-
- /** Returns the service icon. */
- @Nullable
- public Drawable getServiceIcon() {
- return mIcon;
- }
-
- /** Returns the service label. */
- @Nullable
- public CharSequence getServiceLabel() {
- return mLabel;
- }
-
- /** Returns an immutable list of capabilities this provider service can support. */
- @NonNull
- public List<String> getCapabilities() {
- return Collections.unmodifiableList(mCapabilities);
- }
-
- private static @Nullable PackagePolicy getDeviceManagerPolicy(@NonNull Context context) {
try {
- DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+ DevicePolicyManager dpm = newContext.getSystemService(DevicePolicyManager.class);
return dpm.getCredentialManagerPolicy();
} catch (SecurityException e) {
// If the current user is not enrolled in DPM then this can throw a security error.
@@ -374,6 +371,31 @@
}
/**
+ * Returns a valid credential provider that has the given package name. Returns null if no
+ * match is found.
+ */
+ @Nullable
+ public static CredentialProviderInfo getCredentialProviderFromPackageName(
+ @NonNull Context context,
+ int userId,
+ @NonNull String packageName,
+ int providerFilter,
+ @NonNull Set<ServiceInfo> enabledServices) {
+ requireNonNull(context, "context must not be null");
+ requireNonNull(packageName, "package name must not be null");
+ requireNonNull(enabledServices, "enabledServices must not be null");
+
+ for (CredentialProviderInfo credentialProviderInfo : getCredentialProviderServices(context,
+ userId, providerFilter, enabledServices)) {
+ if (credentialProviderInfo.getServiceInfo()
+ .packageName.equals(packageName)) {
+ return credentialProviderInfo;
+ }
+ }
+ return null;
+ }
+
+ /**
* Returns the valid credential provider services available for the user with the given {@code
* userId}.
*/
@@ -381,21 +403,53 @@
public static List<CredentialProviderInfo> getCredentialProviderServices(
@NonNull Context context,
int userId,
- boolean disableSystemAppVerificationForTests,
- int providerFilter) {
+ int providerFilter,
+ Set<ServiceInfo> enabledServices) {
requireNonNull(context, "context must not be null");
// Get the device policy.
- PackagePolicy pp = getDeviceManagerPolicy(context);
+ PackagePolicy pp = getDeviceManagerPolicy(context, userId);
// Generate the provider list.
+ final boolean disableSystemAppVerificationForTests = false;
ProviderGenerator generator =
new ProviderGenerator(
context, pp, disableSystemAppVerificationForTests, providerFilter);
generator.addUserProviders(
- getUserProviders(context, userId, disableSystemAppVerificationForTests));
+ getUserProviders(
+ context, userId, disableSystemAppVerificationForTests, enabledServices));
generator.addSystemProviders(
- getAvailableSystemServices(context, userId, disableSystemAppVerificationForTests));
+ getAvailableSystemServices(
+ context, userId, disableSystemAppVerificationForTests, enabledServices));
+ return generator.getProviders();
+ }
+
+ /**
+ * Returns the valid credential provider services available for the user with the given {@code
+ * userId}. Includes test providers.
+ */
+ @NonNull
+ public static List<CredentialProviderInfo> getCredentialProviderServicesForTesting(
+ @NonNull Context context,
+ int userId,
+ int providerFilter,
+ Set<ServiceInfo> enabledServices) {
+ requireNonNull(context, "context must not be null");
+
+ // Get the device policy.
+ PackagePolicy pp = getDeviceManagerPolicy(context, userId);
+
+ // Generate the provider list.
+ final boolean disableSystemAppVerificationForTests = true;
+ ProviderGenerator generator =
+ new ProviderGenerator(
+ context, pp, disableSystemAppVerificationForTests, providerFilter);
+ generator.addUserProviders(
+ getUserProviders(
+ context, userId, disableSystemAppVerificationForTests, enabledServices));
+ generator.addSystemProviders(
+ getAvailableSystemServices(
+ context, userId, disableSystemAppVerificationForTests, enabledServices));
return generator.getProviders();
}
@@ -484,7 +538,8 @@
private static List<CredentialProviderInfo> getUserProviders(
@NonNull Context context,
@UserIdInt int userId,
- boolean disableSystemAppVerificationForTests) {
+ boolean disableSystemAppVerificationForTests,
+ Set<ServiceInfo> enabledServices) {
final List<CredentialProviderInfo> services = new ArrayList<>();
final List<ResolveInfo> resolveInfos =
context.getPackageManager()
@@ -496,11 +551,12 @@
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
try {
CredentialProviderInfo cpi =
- new CredentialProviderInfo(
+ CredentialProviderInfoFactory.create(
context,
serviceInfo,
/* isSystemProvider= */ false,
- disableSystemAppVerificationForTests);
+ disableSystemAppVerificationForTests,
+ enabledServices.contains(serviceInfo));
if (!cpi.isSystemProvider()) {
services.add(cpi);
}
diff --git a/core/java/android/service/dreams/DreamActivity.java b/core/java/android/service/dreams/DreamActivity.java
index a389223..a2fa139 100644
--- a/core/java/android/service/dreams/DreamActivity.java
+++ b/core/java/android/service/dreams/DreamActivity.java
@@ -58,13 +58,11 @@
setTitle(title);
}
- final Object callback = getIntent().getExtras().getBinder(EXTRA_CALLBACK);
- if (callback instanceof DreamService.DreamActivityCallbacks) {
- mCallback = (DreamService.DreamActivityCallbacks) callback;
+ final Bundle extras = getIntent().getExtras();
+ mCallback = (DreamService.DreamActivityCallbacks) extras.getBinder(EXTRA_CALLBACK);
+
+ if (mCallback != null) {
mCallback.onActivityCreated(this);
- } else {
- mCallback = null;
- finishAndRemoveTask();
}
}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index c7099fd..ce8af83 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1409,6 +1409,10 @@
// Request the DreamOverlay be told to dream with dream's window
// parameters once the window has been attached.
mDreamStartOverlayConsumer = overlay -> {
+ if (mWindow == null) {
+ Slog.d(TAG, "mWindow is null");
+ return;
+ }
try {
overlay.startDream(mWindow.getAttributes(), mOverlayCallback,
mDreamComponent.flattenToString(),
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java b/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java
index ae67068..8409503 100644
--- a/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java
@@ -17,6 +17,8 @@
package android.service.quickaccesswallet;
import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
import android.os.Handler;
@@ -36,13 +38,18 @@
private final IQuickAccessWalletServiceCallbacks mCallback;
private final GetWalletCardsRequest mRequest;
private final Handler mHandler;
+ private final Context mContext;
private boolean mCalled;
- GetWalletCardsCallbackImpl(GetWalletCardsRequest request,
- IQuickAccessWalletServiceCallbacks callback, Handler handler) {
+ GetWalletCardsCallbackImpl(
+ GetWalletCardsRequest request,
+ IQuickAccessWalletServiceCallbacks callback,
+ Handler handler,
+ Context context) {
mRequest = request;
mCallback = callback;
mHandler = handler;
+ mContext = context;
}
/**
@@ -50,11 +57,17 @@
* was successfully handled by the service.
*
* @param response The response contains the list of {@link WalletCard walletCards} to be shown
- * to the user as well as the index of the card that should initially be
- * presented as the selected card.
+ * to the user as well as the index of the card that should initially be presented as the
+ * selected card.
*/
public void onSuccess(@NonNull GetWalletCardsResponse response) {
if (isValidResponse(response)) {
+ // Strip location info from response if the feature is not enabled.
+ if (!mContext.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS)) {
+ removeLocationsFromResponse(response);
+ }
+
mHandler.post(() -> onSuccessInternal(response));
} else {
Log.w(TAG, "Invalid GetWalletCards response");
@@ -152,4 +165,10 @@
}
return true;
}
+
+ private void removeLocationsFromResponse(@NonNull GetWalletCardsResponse response) {
+ for (WalletCard card : response.getWalletCards()) {
+ card.removeCardLocations();
+ }
+ }
}
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
index d004f34..36fa21c 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
@@ -262,8 +262,8 @@
private void onWalletCardsRequestedInternal(
GetWalletCardsRequest request,
IQuickAccessWalletServiceCallbacks callback) {
- onWalletCardsRequested(request,
- new GetWalletCardsCallbackImpl(request, callback, mHandler));
+ onWalletCardsRequested(
+ request, new GetWalletCardsCallbackImpl(request, callback, mHandler, this));
}
private void onTargetActivityIntentRequestedInternal(
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
index e52adcc..4a4fd04 100644
--- a/core/java/android/service/quickaccesswallet/WalletCard.java
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.graphics.drawable.Icon;
+import android.location.Location;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -29,7 +30,8 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-
+import java.util.ArrayList;
+import java.util.List;
/**
* A {@link WalletCard} can represent anything that a user might carry in their wallet -- a credit
@@ -67,6 +69,7 @@
private final Icon mCardIcon;
private final CharSequence mCardLabel;
private final Icon mNonPaymentCardSecondaryImage;
+ private List<Location> mCardLocations;
private WalletCard(Builder builder) {
this.mCardId = builder.mCardId;
@@ -77,6 +80,7 @@
this.mCardIcon = builder.mCardIcon;
this.mCardLabel = builder.mCardLabel;
this.mNonPaymentCardSecondaryImage = builder.mNonPaymentCardSecondaryImage;
+ this.mCardLocations = builder.mCardLocations;
}
/**
@@ -106,7 +110,7 @@
writeIconIfNonNull(mCardIcon, dest, flags);
TextUtils.writeToParcel(mCardLabel, dest, flags);
writeIconIfNonNull(mNonPaymentCardSecondaryImage, dest, flags);
-
+ dest.writeTypedList(mCardLocations, flags);
}
/** Utility function called by writeToParcel
@@ -128,15 +132,20 @@
PendingIntent pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(source);
Icon cardIcon = source.readByte() == 0 ? null : Icon.CREATOR.createFromParcel(source);
CharSequence cardLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
- Icon nonPaymentCardSecondaryImage = source.readByte() == 0 ? null :
- Icon.CREATOR.createFromParcel(source);
- Builder builder = new Builder(cardId, cardType, cardImage, contentDesc, pendingIntent)
- .setCardIcon(cardIcon)
- .setCardLabel(cardLabel);
+ Icon nonPaymentCardSecondaryImage =
+ source.readByte() == 0 ? null : Icon.CREATOR.createFromParcel(source);
+ Builder builder =
+ new Builder(cardId, cardType, cardImage, contentDesc, pendingIntent)
+ .setCardIcon(cardIcon)
+ .setCardLabel(cardLabel);
+ if (cardType == CARD_TYPE_NON_PAYMENT) {
+ builder.setNonPaymentCardSecondaryImage(nonPaymentCardSecondaryImage);
+ }
+ List<Location> cardLocations = new ArrayList<>();
+ source.readTypedList(cardLocations, Location.CREATOR);
+ builder.setCardLocations(cardLocations);
- return cardType == CARD_TYPE_NON_PAYMENT
- ? builder.setNonPaymentCardSecondaryImage(nonPaymentCardSecondaryImage).build() :
- builder.build();
+ return builder.build();
}
@NonNull
@@ -226,13 +235,29 @@
/**
* Visual representation of the card when it is tapped. May include additional information
- * unique to the card, such as a barcode or number. Only valid for CARD_TYPE_NON_PAYMENT.
+ * unique to the card, such as a barcode or number. Only valid for CARD_TYPE_NON_PAYMENT.
*/
@Nullable
public Icon getNonPaymentCardSecondaryImage() {
return mNonPaymentCardSecondaryImage;
}
+ /** List of locations that this card might be useful at. */
+ @NonNull
+ public List<Location> getCardLocations() {
+ return mCardLocations;
+ }
+
+ /**
+ * Removes locations from card. Should be called if {@link
+ * PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is disabled.
+ *
+ * @hide
+ */
+ public void removeCardLocations() {
+ mCardLocations = new ArrayList<>();
+ }
+
/**
* Builder for {@link WalletCard} objects. You must provide cardId, cardImage,
* contentDescription, and pendingIntent. If the card is opaque and should be shown with
@@ -247,6 +272,7 @@
private Icon mCardIcon;
private CharSequence mCardLabel;
private Icon mNonPaymentCardSecondaryImage;
+ private List<Location> mCardLocations = new ArrayList<>();
/**
* @param cardId The card id must be non-null and unique within the list of
@@ -333,18 +359,31 @@
/**
* Visual representation of the card when it is tapped. May include additional information
- * unique to the card, such as a barcode or number. Only valid for CARD_TYPE_NON_PAYMENT.
+ * unique to the card, such as a barcode or number. Only valid for CARD_TYPE_NON_PAYMENT.
*/
@NonNull
- public Builder
- setNonPaymentCardSecondaryImage(@Nullable Icon nonPaymentCardSecondaryImage) {
- Preconditions.checkState(mCardType == CARD_TYPE_NON_PAYMENT,
+ public Builder setNonPaymentCardSecondaryImage(
+ @Nullable Icon nonPaymentCardSecondaryImage) {
+ Preconditions.checkState(
+ mCardType == CARD_TYPE_NON_PAYMENT,
"This field can only be set on non-payment cards");
mNonPaymentCardSecondaryImage = nonPaymentCardSecondaryImage;
return this;
}
/**
+ * Set of locations this card might be useful at. If {@link
+ * PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is enabled, the card might be
+ * shown to the user when a user is near one of these locations.
+ */
+ @NonNull
+ public Builder setCardLocations(@NonNull List<Location> cardLocations) {
+ Preconditions.checkCollectionElementsNotNull(cardLocations, "cardLocations");
+ mCardLocations = cardLocations;
+ return this;
+ }
+
+ /**
* Builds a new {@link WalletCard} instance.
*
* @return A built response.
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index 94384b0..f74f533 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -533,7 +533,7 @@
* the calling package or if the calling user cannot act on behalf of the user from the
* {@code context}.</li>
* <li> {@link IllegalArgumentException} if the user of the {@code context} is not the
- * current user.</li>
+ * current user. Only thrown for apps targeting {@link Build.VERSION_CODES#TIRAMISU}</li>
* </ul>
*/
public static final void requestListeningState(Context context, ComponentName component) {
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 6804c5c..6901b72 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -874,8 +874,11 @@
return;
}
try {
+ // Clearing focus does not expose sensitive data, so set fetch flags to ensure that the
+ // root view is always returned if present.
setAccessibilityFetchFlags(
- AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS);
+ AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS
+ | AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL);
final View root = getRootView();
if (root != null && isShown(root)) {
final View host = mViewRootImpl.mAccessibilityFocusedHost;
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 406f446..24a0355 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -244,6 +244,10 @@
window = iwindow;
}
+ public @Nullable IBinder getWindowToken() {
+ return windowToken;
+ }
+
public IWindow getWindow() {
if (window != null) {
return window;
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index 0a2b06c..867d2b4 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -18,10 +18,14 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.WindowInsets.Type.InsetsType;
import java.util.Arrays;
import java.util.Objects;
@@ -62,19 +66,18 @@
private static final int HAS_INSETS_SIZE = 1;
private static final int HAS_INSETS_SIZE_OVERRIDE = 2;
- private static Rect sTmpRect = new Rect();
- private static Rect sTmpRect2 = new Rect();
+ private static final Rect sTmpRect = new Rect();
+ private static final Rect sTmpRect2 = new Rect();
- /**
- * The type of insets to provide.
- */
- public @InsetsState.InternalInsetsType int type;
+ private final IBinder mOwner;
+ private final int mIndex;
+ private final @InsetsType int mType;
/**
* The source of frame. By default, all adjustment will be based on the window frame, it
* can be set to window bounds or display bounds instead.
*/
- public int source = SOURCE_FRAME;
+ private int mSource = SOURCE_FRAME;
/**
* The provided insets size based on the source frame. The result will be used as the insets
@@ -85,13 +88,13 @@
* (0, 0, 0, 50) instead, the insets frame will be a frame starting from the bottom side of the
* source frame with height of 50, i.e., (0, 150) - (100, 200).
*/
- public Insets insetsSize = null;
+ private Insets mInsetsSize = null;
/**
* If null, the size set in insetsSize will be applied to all window types. If it contains
* element of some types, the insets reported to the window with that types will be overridden.
*/
- public InsetsSizeOverride[] insetsSizeOverrides = null;
+ private InsetsSizeOverride[] mInsetsSizeOverrides = null;
/**
* This field, if set, is indicating the insets needs to be at least the given size inside the
@@ -103,22 +106,80 @@
*
* Be cautious, this will not be in effect for the window types whose insets size is overridden.
*/
- public Insets minimalInsetsSizeInDisplayCutoutSafe = null;
+ private Insets mMinimalInsetsSizeInDisplayCutoutSafe = null;
- public InsetsFrameProvider(int type) {
- this(type, SOURCE_FRAME, null, null);
+ /**
+ * Creates an InsetsFrameProvider which describes what frame an insets source should have.
+ *
+ * @param owner the owner of this provider. We might have multiple sources with the same type on
+ * a display, this is used to identify them.
+ * @param index the index of this provider. An owner might provide multiple sources with the
+ * same type, this is used to identify them.
+ * The value must be in a range of [0, 2047].
+ * @param type the {@link InsetsType}.
+ * @see InsetsSource#createId(Object, int, int)
+ */
+ public InsetsFrameProvider(IBinder owner, @IntRange(from = 0, to = 2047) int index,
+ @InsetsType int type) {
+ if (index < 0 || index >= 2048) {
+ throw new IllegalArgumentException();
+ }
+
+ // This throws IllegalArgumentException if the type is not valid.
+ WindowInsets.Type.indexOf(type);
+
+ mOwner = owner;
+ mIndex = index;
+ mType = type;
}
- public InsetsFrameProvider(int type, Insets insetsSize) {
- this(type, SOURCE_FRAME, insetsSize, null);
+ public IBinder getOwner() {
+ return mOwner;
}
- public InsetsFrameProvider(int type, int source, Insets insetsSize,
- InsetsSizeOverride[] insetsSizeOverride) {
- this.type = type;
- this.source = source;
- this.insetsSize = insetsSize;
- this.insetsSizeOverrides = insetsSizeOverride;
+ public int getIndex() {
+ return mIndex;
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ public InsetsFrameProvider setSource(int source) {
+ mSource = source;
+ return this;
+ }
+
+ public int getSource() {
+ return mSource;
+ }
+
+ public InsetsFrameProvider setInsetsSize(Insets insetsSize) {
+ mInsetsSize = insetsSize;
+ return this;
+ }
+
+ public Insets getInsetsSize() {
+ return mInsetsSize;
+ }
+
+ public InsetsFrameProvider setInsetsSizeOverrides(InsetsSizeOverride[] insetsSizeOverrides) {
+ mInsetsSizeOverrides = insetsSizeOverrides;
+ return this;
+ }
+
+ public InsetsSizeOverride[] getInsetsSizeOverrides() {
+ return mInsetsSizeOverrides;
+ }
+
+ public InsetsFrameProvider setMinimalInsetsSizeInDisplayCutoutSafe(
+ Insets minimalInsetsSizeInDisplayCutoutSafe) {
+ mMinimalInsetsSizeInDisplayCutoutSafe = minimalInsetsSizeInDisplayCutoutSafe;
+ return this;
+ }
+
+ public Insets getMinimalInsetsSizeInDisplayCutoutSafe() {
+ return mMinimalInsetsSizeInDisplayCutoutSafe;
}
@Override
@@ -128,63 +189,73 @@
@Override
public String toString() {
- StringBuilder sb = new StringBuilder(32);
- sb.append("InsetsFrameProvider: {");
- sb.append("type=").append(InsetsState.typeToString(type));
- sb.append(", source=");
- switch (source) {
- case SOURCE_DISPLAY:
- sb.append("SOURCE_DISPLAY");
- break;
- case SOURCE_CONTAINER_BOUNDS:
- sb.append("SOURCE_CONTAINER_BOUNDS Bounds");
- break;
- case SOURCE_FRAME:
- sb.append("SOURCE_FRAME");
- break;
+ final StringBuilder sb = new StringBuilder("InsetsFrameProvider: {");
+ sb.append("owner=").append(mOwner);
+ sb.append(", index=").append(mIndex);
+ sb.append(", type=").append(WindowInsets.Type.toString(mType));
+ sb.append(", source=").append(sourceToString(mSource));
+ if (mInsetsSize != null) {
+ sb.append(", insetsSize=").append(mInsetsSize);
}
- if (insetsSize != null) {
- sb.append(", insetsSize=").append(insetsSize);
- }
- if (insetsSizeOverrides != null) {
- sb.append(", insetsSizeOverrides=").append(Arrays.toString(insetsSizeOverrides));
+ if (mInsetsSizeOverrides != null) {
+ sb.append(", insetsSizeOverrides=").append(Arrays.toString(mInsetsSizeOverrides));
}
sb.append("}");
return sb.toString();
}
+ private static String sourceToString(int source) {
+ switch (source) {
+ case SOURCE_DISPLAY:
+ return "DISPLAY";
+ case SOURCE_CONTAINER_BOUNDS:
+ return "CONTAINER_BOUNDS";
+ case SOURCE_FRAME:
+ return "FRAME";
+ }
+ return "UNDEFINED";
+ }
+
public InsetsFrameProvider(Parcel in) {
+ mOwner = in.readStrongBinder();
+ mIndex = in.readInt();
+ mType = in.readInt();
int insetsSizeModified = in.readInt();
- type = in.readInt();
- source = in.readInt();
+ mSource = in.readInt();
if ((insetsSizeModified & HAS_INSETS_SIZE) != 0) {
- insetsSize = Insets.CREATOR.createFromParcel(in);
+ mInsetsSize = Insets.CREATOR.createFromParcel(in);
}
if ((insetsSizeModified & HAS_INSETS_SIZE_OVERRIDE) != 0) {
- insetsSizeOverrides = in.createTypedArray(InsetsSizeOverride.CREATOR);
+ mInsetsSizeOverrides = in.createTypedArray(InsetsSizeOverride.CREATOR);
}
}
@Override
public void writeToParcel(Parcel out, int flags) {
+ out.writeStrongBinder(mOwner);
+ out.writeInt(mIndex);
+ out.writeInt(mType);
int insetsSizeModified = 0;
- if (insetsSize != null) {
+ if (mInsetsSize != null) {
insetsSizeModified |= HAS_INSETS_SIZE;
}
- if (insetsSizeOverrides != null) {
+ if (mInsetsSizeOverrides != null) {
insetsSizeModified |= HAS_INSETS_SIZE_OVERRIDE;
}
out.writeInt(insetsSizeModified);
- out.writeInt(type);
- out.writeInt(source);
- if (insetsSize != null) {
- insetsSize.writeToParcel(out, flags);
+ out.writeInt(mSource);
+ if (mInsetsSize != null) {
+ mInsetsSize.writeToParcel(out, flags);
}
- if (insetsSizeOverrides != null) {
- out.writeTypedArray(insetsSizeOverrides, flags);
+ if (mInsetsSizeOverrides != null) {
+ out.writeTypedArray(mInsetsSizeOverrides, flags);
}
}
+ public boolean idEquals(InsetsFrameProvider o) {
+ return Objects.equals(mOwner, o.mOwner) && mIndex == o.mIndex && mType == o.mType;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -193,19 +264,21 @@
if (o == null || getClass() != o.getClass()) {
return false;
}
- InsetsFrameProvider other = (InsetsFrameProvider) o;
- return type == other.type && source == other.source
- && Objects.equals(insetsSize, other.insetsSize)
- && Arrays.equals(insetsSizeOverrides, other.insetsSizeOverrides);
+ final InsetsFrameProvider other = (InsetsFrameProvider) o;
+ return Objects.equals(mOwner, other.mOwner) && mIndex == other.mIndex
+ && mType == other.mType && mSource == other.mSource
+ && Objects.equals(mInsetsSize, other.mInsetsSize)
+ && Arrays.equals(mInsetsSizeOverrides, other.mInsetsSizeOverrides);
}
@Override
public int hashCode() {
- return Objects.hash(type, source, insetsSize, Arrays.hashCode(insetsSizeOverrides));
+ return Objects.hash(mOwner, mIndex, mType, mSource, mInsetsSize,
+ Arrays.hashCode(mInsetsSizeOverrides));
}
- public static final @android.annotation.NonNull Parcelable.Creator<InsetsFrameProvider>
- CREATOR = new Parcelable.Creator<InsetsFrameProvider>() {
+ public static final @NonNull Parcelable.Creator<InsetsFrameProvider> CREATOR =
+ new Parcelable.Creator<>() {
@Override
public InsetsFrameProvider createFromParcel(Parcel in) {
return new InsetsFrameProvider(in);
@@ -282,21 +355,28 @@
* directly for that window type.
*/
public static class InsetsSizeOverride implements Parcelable {
- public final int windowType;
- public Insets insetsSize;
+
+ private final int mWindowType;
+ private final Insets mInsetsSize;
protected InsetsSizeOverride(Parcel in) {
- windowType = in.readInt();
- insetsSize = in.readParcelable(null, Insets.class);
+ mWindowType = in.readInt();
+ mInsetsSize = in.readParcelable(null, Insets.class);
}
- public InsetsSizeOverride(int type, Insets size) {
- windowType = type;
- insetsSize = size;
+ public InsetsSizeOverride(int windowType, Insets insetsSize) {
+ mWindowType = windowType;
+ mInsetsSize = insetsSize;
+ }
+ public int getWindowType() {
+ return mWindowType;
}
- public static final Creator<InsetsSizeOverride> CREATOR =
- new Creator<InsetsSizeOverride>() {
+ public Insets getInsetsSize() {
+ return mInsetsSize;
+ }
+
+ public static final Creator<InsetsSizeOverride> CREATOR = new Creator<>() {
@Override
public InsetsSizeOverride createFromParcel(Parcel in) {
return new InsetsSizeOverride(in);
@@ -315,8 +395,8 @@
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeInt(windowType);
- out.writeParcelable(insetsSize, flags);
+ out.writeInt(mWindowType);
+ out.writeParcelable(mInsetsSize, flags);
}
@Override
@@ -324,15 +404,15 @@
StringBuilder sb = new StringBuilder(32);
sb.append("TypedInsetsSize: {");
sb.append("windowType=").append(ViewDebug.intToString(
- WindowManager.LayoutParams.class, "type", windowType));
- sb.append(", insetsSize=").append(insetsSize);
+ WindowManager.LayoutParams.class, "type", mWindowType));
+ sb.append(", insetsSize=").append(mInsetsSize);
sb.append("}");
return sb.toString();
}
@Override
public int hashCode() {
- return Objects.hash(windowType, insetsSize);
+ return Objects.hash(mWindowType, mInsetsSize);
}
}
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 997e4a5..761d504 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -42,7 +42,6 @@
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.ArraySet;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
@@ -707,40 +706,6 @@
&& !WindowConfiguration.inMultiWindowMode(windowingMode);
}
- public static @InternalInsetsType ArraySet<Integer> toInternalType(@InsetsType int types) {
- final ArraySet<Integer> result = new ArraySet<>();
- if ((types & Type.STATUS_BARS) != 0) {
- result.add(ITYPE_STATUS_BAR);
- result.add(ITYPE_CLIMATE_BAR);
- }
- if ((types & Type.NAVIGATION_BARS) != 0) {
- result.add(ITYPE_NAVIGATION_BAR);
- result.add(ITYPE_EXTRA_NAVIGATION_BAR);
- }
- if ((types & Type.SYSTEM_OVERLAYS) != 0) {
- result.add(ITYPE_LEFT_GENERIC_OVERLAY);
- result.add(ITYPE_TOP_GENERIC_OVERLAY);
- result.add(ITYPE_RIGHT_GENERIC_OVERLAY);
- result.add(ITYPE_BOTTOM_GENERIC_OVERLAY);
- }
- if ((types & Type.CAPTION_BAR) != 0) {
- result.add(ITYPE_CAPTION_BAR);
- }
- if ((types & Type.SYSTEM_GESTURES) != 0) {
- result.add(ITYPE_LEFT_GESTURES);
- result.add(ITYPE_TOP_GESTURES);
- result.add(ITYPE_RIGHT_GESTURES);
- result.add(ITYPE_BOTTOM_GESTURES);
- }
- if ((types & Type.MANDATORY_SYSTEM_GESTURES) != 0) {
- result.add(ITYPE_LEFT_MANDATORY_GESTURES);
- result.add(ITYPE_TOP_MANDATORY_GESTURES);
- result.add(ITYPE_RIGHT_MANDATORY_GESTURES);
- result.add(ITYPE_BOTTOM_MANDATORY_GESTURES);
- }
- return result;
- }
-
/**
* Converting a internal type to the public type.
* @param type internal insets type, {@code InternalInsetsType}.
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 85aea85..4a7ed64 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -32,9 +32,10 @@
*
* Use {@link #obtain} to retrieve a new instance of the class when you are going
* to begin tracking. Put the motion events you receive into it with
- * {@link #addMovement(MotionEvent)}. When you want to determine the velocity call
- * {@link #computeCurrentVelocity(int)} and then call {@link #getXVelocity(int)}
- * and {@link #getYVelocity(int)} to retrieve the velocity for each pointer id.
+ * {@link #addMovement(MotionEvent)}. When you want to determine the velocity, call
+ * {@link #computeCurrentVelocity(int)} and then call the velocity-getter methods like
+ * {@link #getXVelocity(int)}, {@link #getYVelocity(int)}, or {@link #getAxisVelocity(int, int)}
+ * to retrieve velocity for different axes and/or pointer IDs.
*/
public final class VelocityTracker {
private static final SynchronizedPool<VelocityTracker> sPool =
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 869efc69..ef76ce3 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -142,7 +142,6 @@
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Transformation;
-import android.view.autofill.AutofillFeatureFlags;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
@@ -8920,7 +8919,8 @@
* @hide
*/
@UnsupportedAppUsage
- public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
+ @TestApi
+ public void getBoundsOnScreen(@NonNull Rect outRect, boolean clipToParent) {
if (mAttachInfo == null) {
return;
}
@@ -8928,6 +8928,9 @@
getBoundsToScreenInternal(position, clipToParent);
outRect.set(Math.round(position.left), Math.round(position.top),
Math.round(position.right), Math.round(position.bottom));
+ // If "Sandboxing View Bounds APIs" override is enabled, applyViewBoundsSandboxingIfNeeded
+ // will sandbox outRect within window bounds.
+ mAttachInfo.mViewRootImpl.applyViewBoundsSandboxingIfNeeded(outRect);
}
/**
@@ -10359,15 +10362,21 @@
private boolean isAutofillable() {
if (getAutofillType() == AUTOFILL_TYPE_NONE) return false;
+ final AutofillManager afm = getAutofillManager();
+ if (afm == null) {
+ return false;
+ }
+
// Disable triggering autofill if the view is integrated with CredentialManager.
- if (AutofillFeatureFlags.shouldIgnoreCredentialViews()
- && isCredential()) return false;
+ if (afm.shouldIgnoreCredentialViews() && isCredential()) {
+ return false;
+ }
if (!isImportantForAutofill()) {
// If view matches heuristics and is not denied, it will be treated same as view that's
// important for autofill
- if (isMatchingAutofillableHeuristics()
- && !isActivityDeniedForAutofillForUnimportantView()) {
+ if (afm.isMatchingAutofillableHeuristics(this)
+ && !afm.isActivityDeniedForAutofillForUnimportantView()) {
return getAutofillViewId() > LAST_APP_AUTOFILL_ID;
}
// View is not important for "regular" autofill, so we must check if Augmented Autofill
@@ -10376,8 +10385,7 @@
if (options == null || !options.isAugmentedAutofillEnabled(mContext)) {
return false;
}
- final AutofillManager afm = getAutofillManager();
- if (afm == null) return false;
+
afm.notifyViewEnteredForAugmentedAutofill(this);
}
@@ -16159,7 +16167,8 @@
* @hide
*/
@UnsupportedAppUsage
- public void getWindowDisplayFrame(Rect outRect) {
+ @TestApi
+ public void getWindowDisplayFrame(@NonNull Rect outRect) {
if (mAttachInfo != null) {
mAttachInfo.mViewRootImpl.getDisplayFrame(outRect);
return;
@@ -26375,6 +26384,9 @@
if (info != null) {
outLocation[0] += info.mWindowLeft;
outLocation[1] += info.mWindowTop;
+ // If OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS override is enabled,
+ // applyViewLocationSandboxingIfNeeded sandboxes outLocation within window bounds.
+ info.mViewRootImpl.applyViewLocationSandboxingIfNeeded(outLocation);
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 807af5b..a8b4da1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
@@ -79,6 +80,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
@@ -92,12 +94,14 @@
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.Size;
import android.annotation.UiContext;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ICompatCameraControlCallback;
import android.app.ResourcesManager;
import android.app.WindowConfiguration;
+import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -895,6 +899,15 @@
private boolean mRelayoutRequested;
+ /**
+ * Whether sandboxing of {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.View#getLocationOnScreen(int[])},
+ * {@link android.view.View#getWindowDisplayFrame} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame}
+ * within Activity bounds is enabled for the current application.
+ */
+ private final boolean mViewBoundsSandboxingEnabled;
+
private int mLastTransformHint = Integer.MIN_VALUE;
private AccessibilityWindowAttributes mAccessibilityWindowAttributes;
@@ -986,6 +999,8 @@
mViewConfiguration,
mContext.getSystemService(InputMethodManager.class));
+ mViewBoundsSandboxingEnabled = getViewBoundsSandboxingEnabled();
+
String processorOverrideName = context.getResources().getString(
R.string.config_inputEventCompatProcessorOverrideClassName);
if (processorOverrideName.isEmpty()) {
@@ -8598,6 +8613,9 @@
*/
void getDisplayFrame(Rect outFrame) {
outFrame.set(mTmpFrames.displayFrame);
+ // Apply sandboxing here (in getter) due to possible layout updates on the client after
+ // mTmpFrames.displayFrame is received from the server.
+ applyViewBoundsSandboxingIfNeeded(outFrame);
}
/**
@@ -8614,6 +8632,69 @@
outFrame.top += insets.top;
outFrame.right -= insets.right;
outFrame.bottom -= insets.bottom;
+ // Apply sandboxing here (in getter) due to possible layout updates on the client after
+ // mTmpFrames.displayFrame is received from the server.
+ applyViewBoundsSandboxingIfNeeded(outFrame);
+ }
+
+ /**
+ * Offset outRect to make it sandboxed within Window's bounds.
+ *
+ * <p>This is used by {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.ViewRootImpl#getDisplayFrame} and
+ * {@link android.view.ViewRootImpl#getWindowVisibleDisplayFrame}, which are invoked by
+ * {@link android.view.View#getWindowDisplayFrame} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame}, as well as
+ * {@link android.view.ViewDebug#captureLayers} for debugging.
+ */
+ void applyViewBoundsSandboxingIfNeeded(final Rect inOutRect) {
+ if (mViewBoundsSandboxingEnabled) {
+ final Rect bounds = getConfiguration().windowConfiguration.getBounds();
+ inOutRect.offset(-bounds.left, -bounds.top);
+ }
+ }
+
+ /**
+ * Offset outLocation to make it sandboxed within Window's bounds.
+ *
+ * <p>This is used by {@link android.view.View#getLocationOnScreen(int[])}
+ */
+ public void applyViewLocationSandboxingIfNeeded(@Size(2) int[] outLocation) {
+ if (mViewBoundsSandboxingEnabled) {
+ final Rect bounds = getConfiguration().windowConfiguration.getBounds();
+ outLocation[0] -= bounds.left;
+ outLocation[1] -= bounds.top;
+ }
+ }
+
+ private boolean getViewBoundsSandboxingEnabled() {
+ // System dialogs (e.g. ANR) can be created within System process, so handleBindApplication
+ // may be never called. This results into all app compat changes being enabled
+ // (see b/268007823) because AppCompatCallbacks.install() is never called with non-empty
+ // array.
+ // With ActivityThread.isSystem we verify that it is not the system process,
+ // then this CompatChange can take effect.
+ if (ActivityThread.isSystem()
+ || !CompatChanges.isChangeEnabled(OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS)) {
+ // It is a system process or OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS change-id is disabled.
+ return false;
+ }
+
+ // OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS is enabled by the device manufacturer.
+ try {
+ final List<PackageManager.Property> properties = mContext.getPackageManager()
+ .queryApplicationProperty(PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS);
+
+ final boolean isOptedOut = !properties.isEmpty() && !properties.get(0).getBoolean();
+ if (isOptedOut) {
+ // PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS is disabled by the app devs.
+ return false;
+ }
+ } catch (RuntimeException e) {
+ // remote exception.
+ }
+
+ return true;
}
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index dfb11bc..35ed88f 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -872,6 +872,42 @@
/**
* Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that it needs to be opted-out from the
+ * compatibility treatment that sandboxes {@link android.view.View} API.
+ *
+ * <p>The treatment can be enabled by device manufacturers for applications which misuse
+ * {@link android.view.View} APIs by expecting that
+ * {@link android.view.View#getLocationOnScreen},
+ * {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.View#getWindowVisibleDisplayFrame},
+ * {@link android.view.View#getWindowDisplayFrame}
+ * return coordinates as if an activity is positioned in the top-left corner of the screen, with
+ * left coordinate equal to 0. This may not be the case for applications in multi-window and in
+ * letterbox modes.
+ *
+ * <p>Setting this property to {@code false} informs the system that the application must be
+ * opted-out from the "Sandbox {@link android.view.View} API to Activity bounds" treatment even
+ * if the device manufacturer has opted the app into the treatment.
+ *
+ * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS"
+ * android:value="false"/>
+ * </application>
+ * </pre>
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
+ "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
+
+ /**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
* .Property} for an app to inform the system that the application can be opted-in or opted-out
* from the compatibility treatment that enables sending a fake focus event for unfocused
* resumed split screen activities. This is needed because some game engines wait to get
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 9504852..5ad2476 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -25,7 +25,6 @@
import android.accessibilityservice.AccessibilityShortcutInfo;
import android.annotation.CallbackExecutor;
import android.annotation.ColorInt;
-import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -76,7 +75,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -140,21 +138,6 @@
public static final int AUTOCLICK_DELAY_DEFAULT = 600;
/**
- * The contrast is defined as a float in [-1, 1], with a default value of 0.
- * @hide
- */
- public static final float CONTRAST_MIN_VALUE = -1f;
-
- /** @hide */
- public static final float CONTRAST_MAX_VALUE = 1f;
-
- /** @hide */
- public static final float CONTRAST_DEFAULT_VALUE = 0f;
-
- /** @hide */
- public static final float CONTRAST_NOT_SET = Float.MIN_VALUE;
-
- /**
* Activity action: Launch UI to manage which accessibility service or feature is assigned
* to the navigation bar Accessibility button.
* <p>
@@ -288,8 +271,6 @@
@UnsupportedAppUsage(trackingBug = 123768939L)
boolean mIsHighTextContrastEnabled;
- private float mUiContrast;
-
boolean mIsAudioDescriptionByDefaultRequested;
// accessibility tracing state
@@ -314,9 +295,6 @@
private final ArrayMap<HighTextContrastChangeListener, Handler>
mHighTextContrastStateChangeListeners = new ArrayMap<>();
- private final ArrayMap<UiContrastChangeListener, Executor>
- mUiContrastChangeListeners = new ArrayMap<>();
-
private final ArrayMap<AccessibilityServicesStateChangeListener, Executor>
mServicesStateChangeListeners = new ArrayMap<>();
@@ -390,7 +368,7 @@
*
* @param manager The manager that is calling back
*/
- void onAccessibilityServicesStateChanged(@NonNull AccessibilityManager manager);
+ void onAccessibilityServicesStateChanged(@NonNull AccessibilityManager manager);
}
/**
@@ -412,21 +390,6 @@
}
/**
- * Listener for the UI contrast. To listen for changes to
- * the UI contrast on the device, implement this interface and
- * register it with the system by calling {@link #addUiContrastChangeListener}.
- */
- public interface UiContrastChangeListener {
-
- /**
- * Called when the color contrast enabled state changes.
- *
- * @param uiContrast The color contrast as in {@link #getUiContrast}
- */
- void onUiContrastChanged(@FloatRange(from = -1.0f, to = 1.0f) float uiContrast);
- }
-
- /**
* Listener for the audio description by default state. To listen for
* changes to the audio description by default state on the device,
* implement this interface and register it with the system by calling
@@ -540,16 +503,6 @@
updateFocusAppearanceLocked(strokeWidth, color);
}
}
-
- @Override
- public void setUiContrast(float contrast) {
- synchronized (mLock) {
- // if value changed in the settings, update the cached value and notify listeners
- if (Math.abs(mUiContrast - contrast) < 1e-10) return;
- mUiContrast = contrast;
- }
- mHandler.obtainMessage(MyCallback.MSG_NOTIFY_CONTRAST_CHANGED).sendToTarget();
- }
};
/**
@@ -720,7 +673,7 @@
/**
* Returns if the high text contrast in the system is enabled.
* <p>
- * <strong>Note:</strong> You need to query this only if your application is
+ * <strong>Note:</strong> You need to query this only if you application is
* doing its own rendering and does not rely on the platform rendering pipeline.
* </p>
*
@@ -740,24 +693,6 @@
}
/**
- * Returns the color contrast for the user.
- * <p>
- * <strong>Note:</strong> You need to query this only if your application is
- * doing its own rendering and does not rely on the platform rendering pipeline.
- * </p>
- * @return The color contrast, float in [-1, 1] where
- * 0 corresponds to the default contrast
- * -1 corresponds to the minimum contrast that the user can set
- * 1 corresponds to the maximum contrast that the user can set
- */
- @FloatRange(from = -1.0f, to = 1.0f)
- public float getUiContrast() {
- synchronized (mLock) {
- return mUiContrast;
- }
- }
-
- /**
* Sends an {@link AccessibilityEvent}.
*
* @param event The event to send.
@@ -1346,35 +1281,6 @@
}
/**
- * Registers a {@link UiContrastChangeListener} for the current user.
- *
- * @param executor The executor on which the listener should be called back.
- * @param listener The listener.
- */
- public void addUiContrastChangeListener(
- @NonNull @CallbackExecutor Executor executor,
- @NonNull UiContrastChangeListener listener) {
- Objects.requireNonNull(executor);
- Objects.requireNonNull(listener);
- synchronized (mLock) {
- mUiContrastChangeListeners.put(listener, executor);
- }
- }
-
- /**
- * Unregisters a {@link UiContrastChangeListener} for the current user.
- * If the listener was not registered, does nothing and returns.
- *
- * @param listener The listener to unregister.
- */
- public void removeUiContrastChangeListener(@NonNull UiContrastChangeListener listener) {
- Objects.requireNonNull(listener);
- synchronized (mLock) {
- mUiContrastChangeListeners.remove(listener);
- }
- }
-
- /**
* Registers a {@link AudioDescriptionRequestedChangeListener}
* for changes in the audio description by default state of the system.
* The value could be read via {@link #isAudioDescriptionRequested}.
@@ -2232,7 +2138,6 @@
mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);
updateUiTimeout(service.getRecommendedTimeoutMillis());
updateFocusAppearanceLocked(service.getFocusStrokeWidth(), service.getFocusColor());
- mUiContrast = service.getUiContrast();
mService = service;
} catch (RemoteException re) {
Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
@@ -2311,22 +2216,6 @@
}
/**
- * Notifies the registered {@link UiContrastChangeListener}s if the value changed.
- */
- private void notifyUiContrastChanged() {
- final ArrayMap<UiContrastChangeListener, Executor> listeners;
- synchronized (mLock) {
- listeners = new ArrayMap<>(mUiContrastChangeListeners);
- }
-
- listeners.entrySet().forEach(entry -> {
- UiContrastChangeListener listener = entry.getKey();
- Executor executor = entry.getValue();
- executor.execute(() -> listener.onUiContrastChanged(mUiContrast));
- });
- }
-
- /**
* Notifies the registered {@link AudioDescriptionStateChangeListener}s.
*/
private void notifyAudioDescriptionbyDefaultStateChanged() {
@@ -2416,7 +2305,6 @@
private final class MyCallback implements Handler.Callback {
public static final int MSG_SET_STATE = 1;
- public static final int MSG_NOTIFY_CONTRAST_CHANGED = 2;
@Override
public boolean handleMessage(Message message) {
@@ -2428,9 +2316,6 @@
setStateLocked(state);
}
} break;
- case MSG_NOTIFY_CONTRAST_CHANGED: {
- notifyUiContrastChanged();
- }
}
return true;
}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 1302421..c828058 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -120,8 +120,6 @@
// Used by UiAutomation for tests on the InputFilter
void injectInputEventToInputFilter(in InputEvent event);
- float getUiContrast();
-
boolean startFlashNotificationSequence(String opPkg, int reason, IBinder token);
boolean stopFlashNotificationSequence(String opPkg);
boolean startFlashNotificationEvent(String opPkg, int reason, String reasonPkg);
diff --git a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
index 931f862..041399c 100644
--- a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
@@ -31,6 +31,4 @@
void setRelevantEventTypes(int eventTypes);
void setFocusAppearance(int strokeWidth, int color);
-
- void setUiContrast(float contrast);
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index ab9f492..14c781b 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -691,6 +691,9 @@
// Indicates whether called the showAutofillDialog() method.
private boolean mShowAutofillDialogCalled = false;
+ // Cached autofill feature flag
+ private boolean mShouldIgnoreCredentialViews = false;
+
private final String[] mFillDialogEnabledHints;
// Tracked all views that have appeared, including views that there are no
@@ -838,6 +841,7 @@
mIsFillDialogEnabled = AutofillFeatureFlags.isFillDialogEnabled();
mFillDialogEnabledHints = AutofillFeatureFlags.getFillDialogEnabledHints();
+ mShouldIgnoreCredentialViews = AutofillFeatureFlags.shouldIgnoreCredentialViews();
if (sDebug) {
Log.d(TAG, "Fill dialog is enabled:" + mIsFillDialogEnabled
+ ", hints=" + Arrays.toString(mFillDialogEnabledHints));
@@ -2081,6 +2085,11 @@
}
/** @hide */
+ public boolean shouldIgnoreCredentialViews() {
+ return mShouldIgnoreCredentialViews;
+ }
+
+ /** @hide */
public void onAuthenticationResult(int authenticationId, Intent data, View focusView) {
if (!hasAutofillFeature()) {
return;
diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java
index d7bca30..5140594 100644
--- a/core/java/android/window/BackNavigationInfo.java
+++ b/core/java/android/window/BackNavigationInfo.java
@@ -92,6 +92,8 @@
@Nullable
private final IOnBackInvokedCallback mOnBackInvokedCallback;
private final boolean mPrepareRemoteAnimation;
+ @Nullable
+ private final CustomAnimationInfo mCustomAnimationInfo;
/**
* Create a new {@link BackNavigationInfo} instance.
@@ -104,11 +106,13 @@
private BackNavigationInfo(@BackTargetType int type,
@Nullable RemoteCallback onBackNavigationDone,
@Nullable IOnBackInvokedCallback onBackInvokedCallback,
- boolean isPrepareRemoteAnimation) {
+ boolean isPrepareRemoteAnimation,
+ @Nullable CustomAnimationInfo customAnimationInfo) {
mType = type;
mOnBackNavigationDone = onBackNavigationDone;
mOnBackInvokedCallback = onBackInvokedCallback;
mPrepareRemoteAnimation = isPrepareRemoteAnimation;
+ mCustomAnimationInfo = customAnimationInfo;
}
private BackNavigationInfo(@NonNull Parcel in) {
@@ -116,6 +120,7 @@
mOnBackNavigationDone = in.readTypedObject(RemoteCallback.CREATOR);
mOnBackInvokedCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder());
mPrepareRemoteAnimation = in.readBoolean();
+ mCustomAnimationInfo = in.readTypedObject(CustomAnimationInfo.CREATOR);
}
/** @hide */
@@ -125,6 +130,7 @@
dest.writeTypedObject(mOnBackNavigationDone, flags);
dest.writeStrongInterface(mOnBackInvokedCallback);
dest.writeBoolean(mPrepareRemoteAnimation);
+ dest.writeTypedObject(mCustomAnimationInfo, flags);
}
/**
@@ -172,6 +178,15 @@
}
}
+ /**
+ * Get customize animation info.
+ * @hide
+ */
+ @Nullable
+ public CustomAnimationInfo getCustomAnimationInfo() {
+ return mCustomAnimationInfo;
+ }
+
/** @hide */
@Override
public int describeContents() {
@@ -197,6 +212,7 @@
+ "mType=" + typeToString(mType) + " (" + mType + ")"
+ ", mOnBackNavigationDone=" + mOnBackNavigationDone
+ ", mOnBackInvokedCallback=" + mOnBackInvokedCallback
+ + ", mCustomizeAnimationInfo=" + mCustomAnimationInfo
+ '}';
}
@@ -223,6 +239,67 @@
}
/**
+ * Information for customize back animation.
+ * @hide
+ */
+ public static final class CustomAnimationInfo implements Parcelable {
+ private final String mPackageName;
+ private int mWindowAnimations;
+
+ /**
+ * The package name of the windowAnimations.
+ */
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * The resource Id of window animations.
+ */
+ public int getWindowAnimations() {
+ return mWindowAnimations;
+ }
+
+ public CustomAnimationInfo(@NonNull String packageName) {
+ this.mPackageName = packageName;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mPackageName);
+ dest.writeInt(mWindowAnimations);
+ }
+
+ private CustomAnimationInfo(@NonNull Parcel in) {
+ mPackageName = in.readString8();
+ mWindowAnimations = in.readInt();
+ }
+
+ @Override
+ public String toString() {
+ return "CustomAnimationInfo, package name= " + mPackageName;
+ }
+
+ @NonNull
+ public static final Creator<CustomAnimationInfo> CREATOR = new Creator<>() {
+ @Override
+ public CustomAnimationInfo createFromParcel(Parcel in) {
+ return new CustomAnimationInfo(in);
+ }
+
+ @Override
+ public CustomAnimationInfo[] newArray(int size) {
+ return new CustomAnimationInfo[size];
+ }
+ };
+ }
+ /**
* @hide
*/
@SuppressWarnings("UnusedReturnValue") // Builder pattern
@@ -233,6 +310,7 @@
@Nullable
private IOnBackInvokedCallback mOnBackInvokedCallback = null;
private boolean mPrepareRemoteAnimation;
+ private CustomAnimationInfo mCustomAnimationInfo;
/**
* @see BackNavigationInfo#getType()
@@ -268,12 +346,22 @@
}
/**
+ * Set windowAnimations for customize animation.
+ */
+ public Builder setWindowAnimations(String packageName, int windowAnimations) {
+ mCustomAnimationInfo = new CustomAnimationInfo(packageName);
+ mCustomAnimationInfo.mWindowAnimations = windowAnimations;
+ return this;
+ }
+
+ /**
* Builds and returns an instance of {@link BackNavigationInfo}
*/
public BackNavigationInfo build() {
return new BackNavigationInfo(mType, mOnBackNavigationDone,
mOnBackInvokedCallback,
- mPrepareRemoteAnimation);
+ mPrepareRemoteAnimation,
+ mCustomAnimationInfo);
}
}
}
diff --git a/core/java/android/window/IBackAnimationRunner.aidl b/core/java/android/window/IBackAnimationRunner.aidl
index 1c67789..b1d7582 100644
--- a/core/java/android/window/IBackAnimationRunner.aidl
+++ b/core/java/android/window/IBackAnimationRunner.aidl
@@ -37,14 +37,13 @@
/**
* Called when the system is ready for the handler to start animating all the visible tasks.
- * @param type The back navigation type.
* @param apps The list of departing (type=MODE_CLOSING) and entering (type=MODE_OPENING)
windows to animate,
* @param wallpapers The list of wallpapers to animate.
* @param nonApps The list of non-app windows such as Bubbles to animate.
* @param finishedCallback The callback to invoke when the animation is finished.
*/
- void onAnimationStart(in int type,
+ void onAnimationStart(
in RemoteAnimationTarget[] apps,
in RemoteAnimationTarget[] wallpapers,
in RemoteAnimationTarget[] nonApps,
diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl
index fd86769..bf9d3c2 100644
--- a/core/java/android/window/ITaskOrganizer.aidl
+++ b/core/java/android/window/ITaskOrganizer.aidl
@@ -34,9 +34,8 @@
* has create a starting window for the Task.
*
* @param info The information about the Task that's available
- * @param appToken Token of the application being started.
*/
- void addStartingWindow(in StartingWindowInfo info, IBinder appToken);
+ void addStartingWindow(in StartingWindowInfo info);
/**
* Called when the Task want to remove the starting window.
diff --git a/core/java/android/window/IWindowlessStartingSurfaceCallback.aidl b/core/java/android/window/IWindowlessStartingSurfaceCallback.aidl
new file mode 100644
index 0000000..a081356
--- /dev/null
+++ b/core/java/android/window/IWindowlessStartingSurfaceCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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.view.SurfaceControl;
+
+/**
+ * Interface to be invoked when a windowless starting surface added.
+ *
+ * @param addedSurface The starting surface.
+ * {@hide}
+ */
+interface IWindowlessStartingSurfaceCallback {
+ void onSurfaceAdded(in SurfaceControl addedSurface);
+}
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index 1a58fd5..071c20f 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -329,6 +329,21 @@
}
/**
+ * Get or create a TaskDescription from a RunningTaskInfo.
+ */
+ public static ActivityManager.TaskDescription getOrCreateTaskDescription(
+ ActivityManager.RunningTaskInfo runningTaskInfo) {
+ final ActivityManager.TaskDescription taskDescription;
+ if (runningTaskInfo.taskDescription != null) {
+ taskDescription = runningTaskInfo.taskDescription;
+ } else {
+ taskDescription = new ActivityManager.TaskDescription();
+ taskDescription.setBackgroundColor(WHITE);
+ }
+ return taskDescription;
+ }
+
+ /**
* Help method to draw the snapshot on a surface.
*/
public static void drawSnapshotOnSurface(StartingWindowInfo info, WindowManager.LayoutParams lp,
@@ -344,13 +359,8 @@
final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams;
final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo;
- final ActivityManager.TaskDescription taskDescription;
- if (runningTaskInfo.taskDescription != null) {
- taskDescription = runningTaskInfo.taskDescription;
- } else {
- taskDescription = new ActivityManager.TaskDescription();
- taskDescription.setBackgroundColor(WHITE);
- }
+ final ActivityManager.TaskDescription taskDescription =
+ getOrCreateTaskDescription(runningTaskInfo);
drawSurface.initiateSystemBarPainter(lp.flags, lp.privateFlags,
attrs.insetsFlags.appearance, taskDescription, info.requestedVisibleTypes);
final Rect systemBarInsets = getSystemBarInsets(windowBounds, topWindowInsetsState);
diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java
index 1b64e61..451acbe 100644
--- a/core/java/android/window/StartingWindowInfo.java
+++ b/core/java/android/window/StartingWindowInfo.java
@@ -22,9 +22,12 @@
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.pm.ActivityInfo;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
import android.view.InsetsState;
+import android.view.SurfaceControl;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager;
@@ -59,6 +62,8 @@
/** @hide **/
public static final int STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN = 4;
+ public static final int STARTING_WINDOW_TYPE_WINDOWLESS = 5;
+
/**
* @hide
*/
@@ -67,7 +72,8 @@
STARTING_WINDOW_TYPE_SPLASH_SCREEN,
STARTING_WINDOW_TYPE_SNAPSHOT,
STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN,
- STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
+ STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN,
+ STARTING_WINDOW_TYPE_WINDOWLESS
})
public @interface StartingWindowType {}
@@ -118,6 +124,7 @@
TYPE_PARAMETER_ACTIVITY_CREATED,
TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN,
TYPE_PARAMETER_ALLOW_HANDLE_SOLID_COLOR_SCREEN,
+ TYPE_PARAMETER_WINDOWLESS,
TYPE_PARAMETER_LEGACY_SPLASH_SCREEN
})
public @interface StartingTypeParams {}
@@ -151,6 +158,12 @@
* @hide
*/
public static final int TYPE_PARAMETER_ALLOW_HANDLE_SOLID_COLOR_SCREEN = 0x00000080;
+
+ /**
+ * Windowless surface
+ */
+ public static final int TYPE_PARAMETER_WINDOWLESS = 0x00000100;
+
/**
* Application is allowed to use the legacy splash screen
* @hide
@@ -182,7 +195,33 @@
*/
public TaskSnapshot taskSnapshot;
- public @InsetsType int requestedVisibleTypes = WindowInsets.Type.defaultVisible();
+ @InsetsType public int requestedVisibleTypes = WindowInsets.Type.defaultVisible();
+
+ /**
+ * App token where the starting window should add to.
+ */
+ public IBinder appToken;
+
+ public IWindowlessStartingSurfaceCallback windowlessStartingSurfaceCallback;
+
+ /**
+ * The root surface where windowless surface should attach on.
+ */
+ public SurfaceControl rootSurface;
+
+ /**
+ * Notify windowless surface is created.
+ * @param addedSurface Created surface.
+ */
+ public void notifyAddComplete(SurfaceControl addedSurface) {
+ if (windowlessStartingSurfaceCallback != null) {
+ try {
+ windowlessStartingSurfaceCallback.onSurfaceAdded(addedSurface);
+ } catch (RemoteException e) {
+ //
+ }
+ }
+ }
public StartingWindowInfo() {
@@ -216,6 +255,9 @@
dest.writeBoolean(isKeyguardOccluded);
dest.writeTypedObject(taskSnapshot, flags);
dest.writeInt(requestedVisibleTypes);
+ dest.writeStrongBinder(appToken);
+ dest.writeStrongInterface(windowlessStartingSurfaceCallback);
+ dest.writeTypedObject(rootSurface, flags);
}
void readFromParcel(@NonNull Parcel source) {
@@ -230,6 +272,10 @@
isKeyguardOccluded = source.readBoolean();
taskSnapshot = source.readTypedObject(TaskSnapshot.CREATOR);
requestedVisibleTypes = source.readInt();
+ appToken = source.readStrongBinder();
+ windowlessStartingSurfaceCallback = IWindowlessStartingSurfaceCallback.Stub
+ .asInterface(source.readStrongBinder());
+ rootSurface = source.readTypedObject(SurfaceControl.CREATOR);
}
@Override
diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java
index 384dacf..5181236 100644
--- a/core/java/android/window/StartingWindowRemovalInfo.java
+++ b/core/java/android/window/StartingWindowRemovalInfo.java
@@ -67,6 +67,16 @@
*/
public float roundedCornerRadius;
+ /**
+ * Remove windowless surface.
+ */
+ public boolean windowlessSurface;
+
+ /**
+ * Remove immediately.
+ */
+ public boolean removeImmediately;
+
public StartingWindowRemovalInfo() {
}
@@ -87,6 +97,8 @@
playRevealAnimation = source.readBoolean();
deferRemoveForIme = source.readBoolean();
roundedCornerRadius = source.readFloat();
+ windowlessSurface = source.readBoolean();
+ removeImmediately = source.readBoolean();
}
@Override
@@ -97,6 +109,8 @@
dest.writeBoolean(playRevealAnimation);
dest.writeBoolean(deferRemoveForIme);
dest.writeFloat(roundedCornerRadius);
+ dest.writeBoolean(windowlessSurface);
+ dest.writeBoolean(removeImmediately);
}
@Override
@@ -105,7 +119,9 @@
+ " frame=" + mainFrame
+ " playRevealAnimation=" + playRevealAnimation
+ " roundedCornerRadius=" + roundedCornerRadius
- + " deferRemoveForIme=" + deferRemoveForIme + "}";
+ + " deferRemoveForIme=" + deferRemoveForIme
+ + " windowlessSurface=" + windowlessSurface
+ + " removeImmediately=" + removeImmediately + "}";
}
public static final @android.annotation.NonNull Creator<StartingWindowRemovalInfo> CREATOR =
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index 02878f8..d4728c1 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -92,13 +92,10 @@
* has create a starting window for the Task.
*
* @param info The information about the Task that's available
- * @param appToken Token of the application being started.
- * context to for resources
* @hide
*/
@BinderThread
- public void addStartingWindow(@NonNull StartingWindowInfo info,
- @NonNull IBinder appToken) {}
+ public void addStartingWindow(@NonNull StartingWindowInfo info) {}
/**
* Called when the Task want to remove the starting window.
@@ -297,9 +294,8 @@
private final ITaskOrganizer mInterface = new ITaskOrganizer.Stub() {
@Override
- public void addStartingWindow(StartingWindowInfo windowInfo,
- IBinder appToken) {
- mExecutor.execute(() -> TaskOrganizer.this.addStartingWindow(windowInfo, appToken));
+ public void addStartingWindow(StartingWindowInfo windowInfo) {
+ mExecutor.execute(() -> TaskOrganizer.this.addStartingWindow(windowInfo));
}
@Override
diff --git a/core/java/android/window/WindowInfosListener.java b/core/java/android/window/WindowInfosListener.java
index 8db5a5e..42bb674 100644
--- a/core/java/android/window/WindowInfosListener.java
+++ b/core/java/android/window/WindowInfosListener.java
@@ -16,6 +16,8 @@
package android.window;
+import android.Manifest;
+import android.annotation.RequiresPermission;
import android.graphics.Matrix;
import android.util.Pair;
import android.util.Size;
@@ -49,10 +51,13 @@
/**
* Register the WindowInfosListener.
*
+ * Registering WindowInfosListeners should only be done within system_server and shell.
+ *
* @return The cached values for InputWindowHandles and DisplayInfos. This is the last updated
* value that was sent from SurfaceFlinger to this particular process. If there was nothing
* registered previously, then the data can be empty.
*/
+ @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER)
public Pair<InputWindowHandle[], DisplayInfo[]> register() {
return nativeRegister(mNativeListener);
}
@@ -65,8 +70,12 @@
}
private static native long nativeCreate(WindowInfosListener thiz);
+
+ @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER)
private static native Pair<InputWindowHandle[], DisplayInfo[]> nativeRegister(long ptr);
+
private static native void nativeUnregister(long ptr);
+
private static native long nativeGetFinalizer();
/**
diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java
new file mode 100644
index 0000000..429156f
--- /dev/null
+++ b/core/java/android/window/WindowInfosListenerForTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 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.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.os.InputConfig;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+import android.view.InputWindowHandle;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
+
+/**
+ * Wrapper class to provide access to WindowInfosListener within tests.
+ *
+ * @hide
+ */
+@TestApi
+public class WindowInfosListenerForTest {
+
+ /**
+ * Window properties passed to {@code @WindowInfosListenerForTest#onWindowInfosChanged}.
+ */
+ public static class WindowInfo {
+ /**
+ * The window's token.
+ */
+ @NonNull
+ public final IBinder windowToken;
+
+ /**
+ * The window's name.
+ */
+ @NonNull
+ public final String name;
+
+ /**
+ * The window's position and size in display space.
+ */
+ @NonNull
+ public final Rect bounds;
+
+ WindowInfo(@NonNull IBinder windowToken, @NonNull String name, @NonNull Rect bounds) {
+ this.windowToken = windowToken;
+ this.name = name;
+ this.bounds = bounds;
+ }
+ }
+
+ private static final String TAG = "WindowInfosListenerForTest";
+
+ private ArrayMap<Consumer<List<WindowInfo>>, WindowInfosListener> mListeners;
+
+ public WindowInfosListenerForTest() {
+ mListeners = new ArrayMap<>();
+ }
+
+ /**
+ * Register a listener that is called when the system's list of visible windows has changes in
+ * position or visibility.
+ *
+ * @param consumer Consumer that is called with reverse Z ordered lists of WindowInfo instances
+ * where the first value is the topmost window.
+ */
+ @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER)
+ public void addWindowInfosListener(
+ @NonNull Consumer<List<WindowInfo>> consumer) {
+ var calledWithInitialState = new CountDownLatch(1);
+ var listener = new WindowInfosListener() {
+ @Override
+ public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
+ DisplayInfo[] displayInfos) {
+ try {
+ calledWithInitialState.await();
+ } catch (InterruptedException exception) {
+ Log.e(TAG,
+ "Exception thrown while waiting for listener to be called with "
+ + "initial state");
+ }
+ consumer.accept(buildWindowInfos(windowHandles));
+ }
+ };
+ mListeners.put(consumer, listener);
+ Pair<InputWindowHandle[], WindowInfosListener.DisplayInfo[]> initialState =
+ listener.register();
+ consumer.accept(buildWindowInfos(initialState.first));
+ calledWithInitialState.countDown();
+ }
+
+ /**
+ * Unregisters the listener.
+ */
+ public void removeWindowInfosListener(@NonNull Consumer<List<WindowInfo>> consumer) {
+ WindowInfosListener listener = mListeners.remove(consumer);
+ if (listener == null) {
+ return;
+ }
+ listener.unregister();
+ }
+
+ private static List<WindowInfo> buildWindowInfos(InputWindowHandle[] windowHandles) {
+ var windowInfos = new ArrayList<WindowInfo>(windowHandles.length);
+ for (var handle : windowHandles) {
+ if ((handle.inputConfig & InputConfig.NOT_VISIBLE) != 0) {
+ continue;
+ }
+ var bounds = new Rect(handle.frameLeft, handle.frameTop, handle.frameRight,
+ handle.frameBottom);
+ windowInfos.add(new WindowInfo(handle.getWindowToken(), handle.name, bounds));
+ }
+ return windowInfos;
+ }
+}
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index 0489dc81..fef5e83 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -17,7 +17,6 @@
import static android.app.admin.DevicePolicyResources.Strings.Core.NOTIFICATION_CHANNEL_DEVICE_ADMIN;
import android.app.INotificationManager;
-import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
@@ -25,7 +24,6 @@
import android.content.pm.ParceledListSlice;
import android.media.AudioAttributes;
import android.os.RemoteException;
-import android.provider.Settings;
import com.android.internal.R;
@@ -78,9 +76,7 @@
final NotificationChannel physicalKeyboardChannel = new NotificationChannel(
PHYSICAL_KEYBOARD,
context.getString(R.string.notification_channel_physical_keyboard),
- NotificationManager.IMPORTANCE_DEFAULT);
- physicalKeyboardChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
- Notification.AUDIO_ATTRIBUTES_DEFAULT);
+ NotificationManager.IMPORTANCE_LOW);
physicalKeyboardChannel.setBlockable(true);
channelsList.add(physicalKeyboardChannel);
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 600ae50..5cab674 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -265,6 +265,34 @@
}
return null;
}
+
+ /** Get animation resId by attribute Id from specific LayoutParams */
+ public int getAnimationResId(LayoutParams lp, int animAttr, int transit) {
+ int resId = Resources.ID_NULL;
+ if (animAttr >= 0) {
+ AttributeCache.Entry ent = getCachedAnimations(lp);
+ if (ent != null) {
+ resId = ent.array.getResourceId(animAttr, 0);
+ }
+ }
+ resId = updateToTranslucentAnimIfNeeded(resId, transit);
+ return resId;
+ }
+
+ /** Get default animation resId */
+ public int getDefaultAnimationResId(int animAttr, int transit) {
+ int resId = Resources.ID_NULL;
+ if (animAttr >= 0) {
+ AttributeCache.Entry ent = getCachedAnimations(DEFAULT_PACKAGE,
+ mDefaultWindowAnimationStyleResId);
+ if (ent != null) {
+ resId = ent.array.getResourceId(animAttr, 0);
+ }
+ }
+ resId = updateToTranslucentAnimIfNeeded(resId, transit);
+ return resId;
+ }
+
/**
* Load animation by attribute Id from a specific AnimationStyle resource.
*
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 4bc567a..ad54004 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -20,20 +20,21 @@
#include <android_runtime/AndroidRuntime.h>
#include <input/InputTransport.h>
+#include <inttypes.h>
#include <log/log.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
#include <utils/Looper.h>
+
+#include <optional>
+#include <unordered_map>
+
#include "android_os_MessageQueue.h"
#include "android_view_InputChannel.h"
#include "android_view_KeyEvent.h"
#include "android_view_MotionEvent.h"
#include "core_jni_helpers.h"
-#include <inttypes.h>
-#include <unordered_map>
-
-
using android::base::Result;
namespace android {
@@ -67,7 +68,7 @@
jobject mSenderWeakGlobal;
InputPublisher mInputPublisher;
sp<MessageQueue> mMessageQueue;
- std::unordered_map<uint32_t, uint32_t> mPublishedSeqMap;
+ std::unordered_map<uint32_t, std::optional<uint32_t>> mPublishedSeqMap;
uint32_t mNextPublishedSeq;
@@ -165,8 +166,14 @@
getInputChannelName().c_str(), status);
return status;
}
+ // mPublishedSeqMap tracks all sequences published from this sender. Only the last
+ // sequence number is used to signal this motion event is finished.
+ if (i == event->getHistorySize()) {
+ mPublishedSeqMap.emplace(publishedSeq, seq);
+ } else {
+ mPublishedSeqMap.emplace(publishedSeq, std::nullopt);
+ }
}
- mPublishedSeqMap.emplace(publishedSeq, seq);
return OK;
}
@@ -277,8 +284,16 @@
// does something wrong and sends bad data. Just ignore and process other events.
return true;
}
- const uint32_t seq = it->second;
+
+ const std::optional<uint32_t> seqOptional = it->second;
mPublishedSeqMap.erase(it);
+ // If this optional does not have a value, it means we are processing an event that had history
+ // and was split. There are more events coming, so we can't call 'dispatchInputEventFinished'
+ // yet. The final split event will have a valid sequence number.
+ if (!seqOptional.has_value()) {
+ return true;
+ }
+ const uint32_t seq = seqOptional.value();
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, pendingEvents=%zu.",
diff --git a/core/jni/android_window_WindowInfosListener.cpp b/core/jni/android_window_WindowInfosListener.cpp
index f2cbe8a..850755a 100644
--- a/core/jni/android_window_WindowInfosListener.cpp
+++ b/core/jni/android_window_WindowInfosListener.cpp
@@ -134,6 +134,7 @@
void destroyNativeService(void* ptr) {
WindowInfosListener* listener = reinterpret_cast<WindowInfosListener*>(ptr);
+ SurfaceComposerClient::getDefault()->removeWindowInfosListener(listener);
listener->decStrong((void*)nativeCreate);
}
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 3c4bac8..b71995f 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -178,3 +178,12 @@
" > $(out)",
tools: ["xmllint"],
}
+
+filegroup {
+ name: "frameworks-base-core-AndroidManifest.xml",
+ srcs: ["AndroidManifest.xml"],
+ visibility: [
+ "//frameworks/base",
+ "//frameworks/base/api",
+ ],
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index cb6c092..8fdefab 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3772,10 +3772,9 @@
<permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"
android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier|role"/>
- <!-- Allows an application to hint that a component lifecycle operation such as sending
- a broadcast is associated with an "interactive" usage scenario.
+ <!-- Allows an application to request interactive options when sending a broadcast.
@hide -->
- <permission android:name="android.permission.COMPONENT_OPTION_INTERACTIVE"
+ <permission android:name="android.permission.BROADCAST_OPTION_INTERACTIVE"
android:protectionLevel="signature|privileged" />
<!-- @SystemApi Must be required by activities that handle the intent action
@@ -8169,6 +8168,10 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+ <service android:name="com.android.server.healthconnect.migration.MigrationBroadcastJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
<service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
android:exported="false">
<intent-filter>
diff --git a/core/res/res/layout/transient_notification.xml b/core/res/res/layout/transient_notification.xml
index 3259201..8bedb89 100644
--- a/core/res/res/layout/transient_notification.xml
+++ b/core/res/res/layout/transient_notification.xml
@@ -25,7 +25,7 @@
android:orientation="horizontal"
android:gravity="center_vertical"
android:maxWidth="@dimen/toast_width"
- android:background="?android:attr/toastFrameBackground"
+ android:background="?android:attr/colorBackground"
android:elevation="@dimen/toast_elevation"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
diff --git a/core/res/res/layout/transient_notification_with_icon.xml b/core/res/res/layout/transient_notification_with_icon.xml
index e9b17df..0dfb3ad 100644
--- a/core/res/res/layout/transient_notification_with_icon.xml
+++ b/core/res/res/layout/transient_notification_with_icon.xml
@@ -22,7 +22,7 @@
android:orientation="horizontal"
android:gravity="center_vertical"
android:maxWidth="@dimen/toast_width"
- android:background="?android:attr/toastFrameBackground"
+ android:background="?android:attr/colorBackground"
android:elevation="@dimen/toast_elevation"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ffb602d..16511a6 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6346,4 +6346,8 @@
<bool name="config_batteryStatsResetOnUnplugHighBatteryLevel">true</bool>
<!-- Whether to reset Battery Stats on unplug if the battery was significantly charged -->
<bool name="config_batteryStatsResetOnUnplugAfterSignificantCharge">true</bool>
+
+ <!-- Whether we should persist the brightness value in nits for the default display even if
+ the underlying display device changes. -->
+ <bool name="config_persistBrightnessNitsForDefaultDisplay">false</bool>
</resources>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index ebda172..c0f2157 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -52,6 +52,16 @@
<integer name="config_delay_for_ims_dereg_millis">0</integer>
<java-symbol type="integer" name="config_delay_for_ims_dereg_millis" />
+ <!-- Define the bar of considering the availability of a subscription is stable in milliseconds,
+ where 0 means immediate switch, and negative milliseconds indicates the auto data switch
+ feature is disabled.-->
+ <integer name="auto_data_switch_availability_stability_time_threshold_millis">10000</integer>
+ <java-symbol type="integer" name="auto_data_switch_availability_stability_time_threshold_millis" />
+
+ <!-- Define the maximum retry times when a validation for switching failed.-->
+ <integer name="auto_data_switch_validation_max_retry">7</integer>
+ <java-symbol type="integer" name="auto_data_switch_validation_max_retry" />
+
<!-- Boolean indicating whether the Iwlan data service supports persistence of iwlan ipsec
tunnels across service restart. If iwlan tunnels are not persisted across restart,
Framework will clean up dangling data connections when service restarts -->
@@ -114,6 +124,11 @@
<string name="config_pointing_ui_package" translatable="false"></string>
<java-symbol type="string" name="config_pointing_ui_package" />
+ <!-- Telephony resends received satellite datagram to listener
+ if ack is not received within this timeout -->
+ <integer name="config_timeout_to_receive_delivered_ack_millis">300000</integer>
+ <java-symbol type="integer" name="config_timeout_to_receive_delivered_ack_millis" />
+
<!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks
will not perform handover if the target transport is out of service, or VoPS not
supported. The network will be torn down on the source transport, and will be
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 07f3530..3074906 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3787,8 +3787,10 @@
<!-- Title of the physical keyboard category in the input method selector [CHAR LIMIT=30] -->
<string name="hardware">Show virtual keyboard</string>
- <!-- Title of the notification to prompt the user to configure physical keyboard settings. -->
- <string name="select_keyboard_layout_notification_title">Configure physical keyboard</string>
+ <!-- Title of the notification to prompt the user to configure physical keyboard settings. [CHAR LIMIT=NOTIF_TITLE] -->
+ <string name="select_keyboard_layout_notification_title">Configure <xliff:g id="device_name" example="Foobar USB Keyboard">%s</xliff:g></string>
+ <!-- Title of the notification to prompt the user to configure physical keyboard settings when multiple keyboards connected. [CHAR LIMIT=NOTIF_TITLE] -->
+ <string name="select_multiple_keyboards_layout_notification_title">Configure physical keyboards</string>
<!-- Message of the notification to prompt the user to configure physical keyboard settings
where the user can associate language with physical keyboard layout. -->
<string name="select_keyboard_layout_notification_message">Tap to select language and layout</string>
@@ -6264,4 +6266,19 @@
<string name="concurrent_display_notification_thermal_content">Dual Screen is unavailable because your phone is getting too warm</string>
<!-- Text of device state notification turn off button. [CHAR LIMIT=NONE] -->
<string name="device_state_notification_turn_off_button">Turn off</string>
+
+ <!-- Notification title when a keyboard has been configured [CHAR LIMIT=NOTIF_TITLE] -->
+ <string name="keyboard_layout_notification_selected_title"><xliff:g id="device_name" example="Foobar USB Keyboard">%s</xliff:g> configured</string>
+ <!-- Notification message shown when one layout was configured for a physical keyboard [CHAR LIMIT=NOTIF_BODY] -->
+ <string name="keyboard_layout_notification_one_selected_message">Keyboard layout set to <xliff:g id="layout_1" example="English (US)">%s</xliff:g>. Tap to change.</string>
+ <!-- Notification message shown when two layout were configured for a physical keyboard [CHAR LIMIT=NOTIF_BODY] -->
+ <string name="keyboard_layout_notification_two_selected_message">Keyboard layout set to <xliff:g id="layout_1" example="English (US)">%1$s</xliff:g>, <xliff:g id="layout_2" example="German">%2$s</xliff:g>. Tap to change.</string>
+ <!-- Notification message shown when three layout were configured for a physical keyboard [CHAR LIMIT=NOTIF_BODY] -->
+ <string name="keyboard_layout_notification_three_selected_message">Keyboard layout set to <xliff:g id="layout_1" example="English (US)">%1$s</xliff:g>, <xliff:g id="layout_2" example="German">%2$s</xliff:g>, <xliff:g id="layout_3" example="French">%3$s</xliff:g>. Tap to change.</string>
+ <!-- Notification message shown when more than three layout were configured for a physical keyboard [CHAR LIMIT=NOTIF_BODY] -->
+ <string name="keyboard_layout_notification_more_than_three_selected_message">Keyboard layout set to <xliff:g id="layout_1" example="English (US)">%1$s</xliff:g>, <xliff:g id="layout_2" example="English (US)">%2$s</xliff:g>, <xliff:g id="layout_3" example="French">%3$s</xliff:g>\u2026 Tap to change.</string>
+ <!-- Notification title when multiple keyboards with selected layouts have been connected the first time simultaneously [CHAR LIMIT=NOTIF_TITLE] -->
+ <string name="keyboard_layout_notification_multiple_selected_title">Physical keyboards configured</string>
+ <!-- Notification message when multiple keyboards with selected layouts have been connected the first time simultaneously [CHAR LIMIT=NOTIF_BODY] -->
+ <string name="keyboard_layout_notification_multiple_selected_message">Tap to view keyboards</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 92dc569..c34d31c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2138,6 +2138,7 @@
<java-symbol type="string" name="report" />
<java-symbol type="string" name="select_input_method" />
<java-symbol type="string" name="select_keyboard_layout_notification_title" />
+ <java-symbol type="string" name="select_multiple_keyboards_layout_notification_title" />
<java-symbol type="string" name="select_keyboard_layout_notification_message" />
<java-symbol type="string" name="smv_application" />
<java-symbol type="string" name="smv_process" />
@@ -2229,6 +2230,7 @@
<java-symbol type="bool" name="config_preventImeStartupUnlessTextEditor" />
<java-symbol type="array" name="config_nonPreemptibleInputMethods" />
<java-symbol type="bool" name="config_enhancedConfirmationModeEnabled" />
+ <java-symbol type="bool" name="config_persistBrightnessNitsForDefaultDisplay" />
<java-symbol type="layout" name="resolver_list" />
<java-symbol type="id" name="resolver_list" />
@@ -4964,10 +4966,19 @@
<!-- Whether to show weather on the lockscreen by default. -->
<java-symbol type="bool" name="config_lockscreenWeatherEnabledByDefault" />
+ <!-- For keyboard notification -->
+ <java-symbol type="string" name="keyboard_layout_notification_selected_title"/>
+ <java-symbol type="string" name="keyboard_layout_notification_one_selected_message"/>
+ <java-symbol type="string" name="keyboard_layout_notification_two_selected_message"/>
+ <java-symbol type="string" name="keyboard_layout_notification_three_selected_message"/>
+ <java-symbol type="string" name="keyboard_layout_notification_more_than_three_selected_message"/>
+ <java-symbol type="string" name="keyboard_layout_notification_multiple_selected_title"/>
+ <java-symbol type="string" name="keyboard_layout_notification_multiple_selected_message"/>
+
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
-
+
<java-symbol name="materialColorOnSecondaryFixedVariant" type="attr"/>
<java-symbol name="materialColorOnTertiaryFixedVariant" type="attr"/>
<java-symbol name="materialColorSurfaceContainerLowest" type="attr"/>
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
deleted file mode 100644
index 17b064c..0000000
--- a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
+++ /dev/null
@@ -1,123 +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 android.companion.virtual.sensor;
-
-import static android.hardware.Sensor.TYPE_ACCELEROMETER;
-import static android.hardware.SensorDirectChannel.RATE_STOP;
-import static android.hardware.SensorDirectChannel.RATE_VERY_FAST;
-import static android.hardware.SensorDirectChannel.TYPE_HARDWARE_BUFFER;
-import static android.hardware.SensorDirectChannel.TYPE_MEMORY_FILE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.hardware.Sensor;
-import android.os.Parcel;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class VirtualSensorConfigTest {
-
- private static final String SENSOR_NAME = "VirtualSensorName";
- private static final String SENSOR_VENDOR = "VirtualSensorVendor";
-
- @Test
- public void parcelAndUnparcel_matches() {
- final VirtualSensorConfig originalConfig =
- new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
- .setVendor(SENSOR_VENDOR)
- .setHighestDirectReportRateLevel(RATE_VERY_FAST)
- .setDirectChannelTypesSupported(TYPE_MEMORY_FILE)
- .build();
- final Parcel parcel = Parcel.obtain();
- originalConfig.writeToParcel(parcel, /* flags= */ 0);
- parcel.setDataPosition(0);
- final VirtualSensorConfig recreatedConfig =
- VirtualSensorConfig.CREATOR.createFromParcel(parcel);
- assertThat(recreatedConfig.getType()).isEqualTo(originalConfig.getType());
- assertThat(recreatedConfig.getName()).isEqualTo(originalConfig.getName());
- assertThat(recreatedConfig.getVendor()).isEqualTo(originalConfig.getVendor());
- assertThat(recreatedConfig.getHighestDirectReportRateLevel()).isEqualTo(RATE_VERY_FAST);
- assertThat(recreatedConfig.getDirectChannelTypesSupported()).isEqualTo(TYPE_MEMORY_FILE);
- // From hardware/libhardware/include/hardware/sensors-base.h:
- // 0x400 is SENSOR_FLAG_DIRECT_CHANNEL_ASHMEM (i.e. TYPE_MEMORY_FILE)
- // 0x800 is SENSOR_FLAG_DIRECT_CHANNEL_GRALLOC (i.e. TYPE_HARDWARE_BUFFER)
- // 7 is SENSOR_FLAG_SHIFT_DIRECT_REPORT
- assertThat(recreatedConfig.getFlags()).isEqualTo(0x400 | RATE_VERY_FAST << 7);
- }
-
- @Test
- public void virtualSensorConfig_invalidName_throwsException() {
- assertThrows(
- NullPointerException.class,
- () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, null));
- }
-
- @Test
- public void virtualSensorConfig_invalidType_throwsException() {
- assertThrows(
- IllegalArgumentException.class,
- () -> new VirtualSensorConfig.Builder(Sensor.TYPE_ALL, SENSOR_NAME));
-
- assertThrows(
- IllegalArgumentException.class,
- () -> new VirtualSensorConfig.Builder(0, SENSOR_NAME));
- }
-
- @Test
- public void hardwareBufferDirectChannelTypeSupported_throwsException() {
- assertThrows(
- IllegalArgumentException.class,
- () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
- .setDirectChannelTypesSupported(TYPE_HARDWARE_BUFFER | TYPE_MEMORY_FILE));
- }
-
- @Test
- public void directChannelTypeSupported_missingHighestReportRateLevel_throwsException() {
- assertThrows(
- IllegalArgumentException.class,
- () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
- .setDirectChannelTypesSupported(TYPE_MEMORY_FILE)
- .build());
- }
-
- @Test
- public void directChannelTypeSupported_missingDirectChannelTypeSupported_throwsException() {
- assertThrows(
- IllegalArgumentException.class,
- () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
- .setHighestDirectReportRateLevel(RATE_VERY_FAST)
- .build());
- }
-
- @Test
- public void sensorConfig_onlyRequiredFields() {
- final VirtualSensorConfig config =
- new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME).build();
- assertThat(config.getVendor()).isNull();
- assertThat(config.getHighestDirectReportRateLevel()).isEqualTo(RATE_STOP);
- assertThat(config.getDirectChannelTypesSupported()).isEqualTo(0);
- assertThat(config.getFlags()).isEqualTo(0);
- }
-}
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java
deleted file mode 100644
index c260ef9..0000000
--- a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java
+++ /dev/null
@@ -1,82 +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 android.companion.virtual.sensor;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.os.Parcel;
-import android.os.SystemClock;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class VirtualSensorEventTest {
-
- private static final long TIMESTAMP_NANOS = SystemClock.elapsedRealtimeNanos();
- private static final float[] SENSOR_VALUES = new float[] {1.2f, 3.4f, 5.6f};
-
- @Test
- public void parcelAndUnparcel_matches() {
- final VirtualSensorEvent originalEvent = new VirtualSensorEvent.Builder(SENSOR_VALUES)
- .setTimestampNanos(TIMESTAMP_NANOS)
- .build();
- final Parcel parcel = Parcel.obtain();
- originalEvent.writeToParcel(parcel, /* flags= */ 0);
- parcel.setDataPosition(0);
- final VirtualSensorEvent recreatedEvent =
- VirtualSensorEvent.CREATOR.createFromParcel(parcel);
- assertThat(recreatedEvent.getValues()).isEqualTo(originalEvent.getValues());
- assertThat(recreatedEvent.getTimestampNanos()).isEqualTo(originalEvent.getTimestampNanos());
- }
-
- @Test
- public void sensorEvent_nullValues() {
- assertThrows(
- IllegalArgumentException.class, () -> new VirtualSensorEvent.Builder(null).build());
- }
-
- @Test
- public void sensorEvent_noValues() {
- assertThrows(
- IllegalArgumentException.class,
- () -> new VirtualSensorEvent.Builder(new float[0]).build());
- }
-
- @Test
- public void sensorEvent_noTimestamp_usesCurrentTime() {
- final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES).build();
- assertThat(event.getValues()).isEqualTo(SENSOR_VALUES);
- assertThat(TIMESTAMP_NANOS).isLessThan(event.getTimestampNanos());
- assertThat(event.getTimestampNanos()).isLessThan(SystemClock.elapsedRealtimeNanos());
- }
-
- @Test
- public void sensorEvent_created() {
- final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES)
- .setTimestampNanos(TIMESTAMP_NANOS)
- .build();
- assertThat(event.getTimestampNanos()).isEqualTo(TIMESTAMP_NANOS);
- assertThat(event.getValues()).isEqualTo(SENSOR_VALUES);
- }
-}
diff --git a/core/tests/coretests/src/android/content/pm/TEST_MAPPING b/core/tests/coretests/src/android/content/pm/TEST_MAPPING
index 15e04d1..978d80c 100644
--- a/core/tests/coretests/src/android/content/pm/TEST_MAPPING
+++ b/core/tests/coretests/src/android/content/pm/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "presubmit-large": [
+ "presubmit": [
{
"name": "FrameworksCoreTests",
"options": [
diff --git a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
index 43334ab..e31d5ae 100644
--- a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
+++ b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
@@ -30,6 +30,7 @@
import android.app.Activity;
import android.app.slice.Slice;
import android.content.Context;
+import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -47,6 +48,7 @@
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -59,6 +61,17 @@
@Mock
private Activity mMockActivity;
+ private static final int TEST_USER_ID = 1;
+ private static final CredentialProviderInfo TEST_CREDENTIAL_PROVIDER_INFO =
+ new CredentialProviderInfo.Builder(new ServiceInfo())
+ .setSystemProvider(true)
+ .setOverrideLabel("test")
+ .addCapabilities(Arrays.asList("passkey"))
+ .setEnabled(true)
+ .build();
+ private static final List<CredentialProviderInfo> TEST_CREDENTIAL_PROVIDER_INFO_LIST =
+ Arrays.asList(TEST_CREDENTIAL_PROVIDER_INFO);
+
private GetCredentialRequest mGetRequest;
private CreateCredentialRequest mCreateRequest;
@@ -438,95 +451,53 @@
}
@Test
- public void testListEnabledProviders_nullExecutor() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.listEnabledProviders(null, null, result -> {
- }));
-
+ public void testGetCredentialProviderServices_allProviders() throws RemoteException {
+ verifyGetCredentialProviderServices(CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS);
}
@Test
- public void testListEnabledProviders_nullCallback() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.listEnabledProviders(null, mExecutor, null));
-
+ public void testGetCredentialProviderServices_userProviders() throws RemoteException {
+ verifyGetCredentialProviderServices(CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY);
}
@Test
- public void testListEnabledProviders_alreadyCancelled() throws RemoteException {
- final CancellationSignal cancellation = new CancellationSignal();
- cancellation.cancel();
-
- mCredentialManager.listEnabledProviders(cancellation, mExecutor, result -> {
- });
-
- verify(mMockCredentialManagerService, never()).listEnabledProviders(any());
+ public void testGetCredentialProviderServices_systemProviders() throws RemoteException {
+ verifyGetCredentialProviderServices(CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY);
}
@Test
- public void testListEnabledProviders_cancel() throws RemoteException {
- final ICancellationSignal serviceSignal = mock(ICancellationSignal.class);
- final CancellationSignal cancellation = new CancellationSignal();
-
- OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException> callback =
- mock(OutcomeReceiver.class);
-
- when(mMockCredentialManagerService.listEnabledProviders(any())).thenReturn(serviceSignal);
-
- mCredentialManager.listEnabledProviders(cancellation, mExecutor, callback);
-
- verify(mMockCredentialManagerService).listEnabledProviders(any());
-
- cancellation.cancel();
- verify(serviceSignal).cancel();
+ public void testGetCredentialProviderServicesForTesting_allProviders() throws RemoteException {
+ verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS);
}
@Test
- public void testListEnabledProviders_failed() throws RemoteException {
- ArgumentCaptor<IListEnabledProvidersCallback> callbackCaptor = ArgumentCaptor.forClass(
- IListEnabledProvidersCallback.class);
- ArgumentCaptor<ListEnabledProvidersException> errorCaptor = ArgumentCaptor.forClass(
- ListEnabledProvidersException.class);
-
- OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException> callback =
- mock(OutcomeReceiver.class);
-
- when(mMockCredentialManagerService.listEnabledProviders(
- callbackCaptor.capture())).thenReturn(mock(ICancellationSignal.class));
- mCredentialManager.listEnabledProviders(null, mExecutor, callback);
- verify(mMockCredentialManagerService).listEnabledProviders(any());
-
- final String errorType = "type";
- callbackCaptor.getValue().onError("type", "unknown error");
- verify(callback).onError(errorCaptor.capture());
-
- assertThat(errorCaptor.getValue().getType()).isEqualTo(errorType);
+ public void testGetCredentialProviderServicesForTesting_userProviders() throws RemoteException {
+ verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY);
}
@Test
- public void testListEnabledProviders_success() throws RemoteException {
- ListEnabledProvidersResponse response = ListEnabledProvidersResponse.create(
- List.of("foo", "bar", "baz"));
+ public void testGetCredentialProviderServicesForTesting_systemProviders() throws RemoteException {
+ verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY);
+ }
- OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException> callback =
- mock(OutcomeReceiver.class);
+ private void verifyGetCredentialProviderServices(int testFilter) throws RemoteException {
+ when(mMockCredentialManagerService.getCredentialProviderServices(
+ TEST_USER_ID, testFilter)).thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
- ArgumentCaptor<IListEnabledProvidersCallback> callbackCaptor = ArgumentCaptor.forClass(
- IListEnabledProvidersCallback.class);
- ArgumentCaptor<ListEnabledProvidersResponse> responseCaptor = ArgumentCaptor.forClass(
- ListEnabledProvidersResponse.class);
+ List<CredentialProviderInfo> output =
+ mCredentialManager.getCredentialProviderServices(TEST_USER_ID, testFilter);
- when(mMockCredentialManagerService.listEnabledProviders(
- callbackCaptor.capture())).thenReturn(mock(ICancellationSignal.class));
- mCredentialManager.listEnabledProviders(null, mExecutor, callback);
+ assertThat(output).containsExactlyElementsIn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
+ }
- verify(mMockCredentialManagerService).listEnabledProviders(any());
+ private void verifyGetCredentialProviderServicesForTesting(int testFilter) throws RemoteException {
+ when(mMockCredentialManagerService.getCredentialProviderServicesForTesting(
+ testFilter)).thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
- callbackCaptor.getValue().onResponse(response);
+ List<CredentialProviderInfo> output =
+ mCredentialManager.getCredentialProviderServicesForTesting(testFilter);
- verify(callback).onResult(responseCaptor.capture());
- assertThat(responseCaptor.getValue().getProviderComponentNames()).containsExactlyElementsIn(
- response.getProviderComponentNames());
+ assertThat(output).containsExactlyElementsIn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
}
@Test
diff --git a/core/tests/coretests/src/android/view/AccessibilityInteractionControllerTest.java b/core/tests/coretests/src/android/view/AccessibilityInteractionControllerTest.java
index 7855ef9..d91541f 100644
--- a/core/tests/coretests/src/android/view/AccessibilityInteractionControllerTest.java
+++ b/core/tests/coretests/src/android/view/AccessibilityInteractionControllerTest.java
@@ -16,8 +16,8 @@
package android.view;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
@@ -98,24 +98,42 @@
@Test
public void clearAccessibilityFocus_shouldClearFocus() throws Exception {
performAccessibilityFocus("com.android.frameworks.coretests:id/appNameBtn");
- assertTrue("Button should have a11y focus",
- mButton.isAccessibilityFocused());
+ assertWithMessage("Button should have a11y focus").that(
+ mButton.isAccessibilityFocused()).isTrue();
mAccessibilityInteractionController.clearAccessibilityFocusClientThread();
sInstrumentation.waitForIdleSync();
- assertFalse("Button should not have a11y focus",
- mButton.isAccessibilityFocused());
+ assertWithMessage("Button should not have a11y focus").that(
+ mButton.isAccessibilityFocused()).isFalse();
}
@Test
public void clearAccessibilityFocus_uiThread_shouldClearFocus() throws Exception {
performAccessibilityFocus("com.android.frameworks.coretests:id/appNameBtn");
- assertTrue("Button should have a11y focus",
- mButton.isAccessibilityFocused());
- sInstrumentation.runOnMainSync(() -> {
- mAccessibilityInteractionController.clearAccessibilityFocusClientThread();
- });
- assertFalse("Button should not have a11y focus",
- mButton.isAccessibilityFocused());
+ assertWithMessage("Button should have a11y focus").that(
+ mButton.isAccessibilityFocused()).isTrue();
+ sInstrumentation.runOnMainSync(() ->
+ mAccessibilityInteractionController.clearAccessibilityFocusClientThread());
+ assertWithMessage("Button should not have a11y focus").that(
+ mButton.isAccessibilityFocused()).isFalse();
+ }
+
+ @Test
+ public void clearAccessibilityFocus_sensitiveRootView_shouldClearFocus()
+ throws Exception {
+ final View rootView = mButton.getRootView();
+ assertThat(rootView).isNotNull();
+ try {
+ rootView.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES);
+ performAccessibilityFocus("com.android.frameworks.coretests:id/appNameBtn");
+ assertWithMessage("Button should have a11y focus").that(
+ mButton.isAccessibilityFocused()).isTrue();
+ sInstrumentation.runOnMainSync(() ->
+ mAccessibilityInteractionController.clearAccessibilityFocusClientThread());
+ assertWithMessage("Button should not have a11y focus").that(
+ mButton.isAccessibilityFocused()).isFalse();
+ } finally {
+ rootView.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_AUTO);
+ }
}
private void launchActivity() {
diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS
index a142e27..2ca9994 100644
--- a/core/tests/coretests/src/android/view/OWNERS
+++ b/core/tests/coretests/src/android/view/OWNERS
@@ -1,4 +1,5 @@
# Accessibility
+per-file AccessibilityInteractionControllerTest.java = file:/services/accessibility/OWNERS
per-file WindowInfoTest.java = file:/services/accessibility/OWNERS
# Input
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 116aa489..fe639ff 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -24,7 +24,7 @@
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
<permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
<permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
- <permission name="android.permission.COMPONENT_OPTION_INTERACTIVE"/>
+ <permission name="android.permission.BROADCAST_OPTION_INTERACTIVE"/>
<permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/>
<permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
<permission name="android.permission.CONTROL_VPN"/>
diff --git a/data/etc/preinstalled-packages-platform-overlays.xml b/data/etc/preinstalled-packages-platform-overlays.xml
index 6f6390b..9959433 100644
--- a/data/etc/preinstalled-packages-platform-overlays.xml
+++ b/data/etc/preinstalled-packages-platform-overlays.xml
@@ -53,6 +53,9 @@
<install-in-user-type package="com.android.internal.systemui.onehanded.gestural">
<install-in user-type="FULL" />
</install-in-user-type>
+ <install-in-user-type package="com.android.internal.systemui.navbar.transparent">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
<install-in-user-type package="com.android.theme.color.amethyst">
<install-in user-type="FULL" />
<install-in user-type="PROFILE" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 5cb5ffa0..4a98c4d 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -539,6 +539,12 @@
<permission name="android.permission.BIND_WALLPAPER"/>
</privapp-permissions>
+ <privapp-permissions package="com.android.wallpaper">
+ <permission name="android.permission.SET_WALLPAPER_COMPONENT"/>
+ <permission name="android.permission.BIND_WALLPAPER"/>
+ <permission name="android.permission.CUSTOMIZE_SYSTEM_UI"/>
+ </privapp-permissions>
+
<privapp-permissions package="com.android.dynsystem">
<permission name="android.permission.REBOOT"/>
<permission name="android.permission.MANAGE_DYNAMIC_SYSTEM"/>
diff --git a/data/keyboards/Vendor_004c_Product_0265.idc b/data/keyboards/Vendor_004c_Product_0265.idc
new file mode 120000
index 0000000..707dfcf
--- /dev/null
+++ b/data/keyboards/Vendor_004c_Product_0265.idc
@@ -0,0 +1 @@
+Vendor_05ac_Product_0265.idc
\ No newline at end of file
diff --git a/data/keyboards/Vendor_03f6_Product_a001.idc b/data/keyboards/Vendor_03f6_Product_a001.idc
new file mode 100644
index 0000000..bcb4ee3
--- /dev/null
+++ b/data/keyboards/Vendor_03f6_Product_a001.idc
@@ -0,0 +1,22 @@
+# Copyright 2023 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.
+
+#
+# Brydge Touchpad
+#
+
+# Reports from this touchpad sometimes get bunched together due to Bluetooth
+# batching, leading to bad timestamps that mess up finger velocity calculations.
+# To fix this, set a fake delta using the touchpad's known report rate.
+gestureProp.Fake_Timestamp_Delta = 0.010
diff --git a/data/keyboards/Vendor_046d_Product_4011.idc b/data/keyboards/Vendor_046d_Product_4011.idc
new file mode 100644
index 0000000..3a23830
--- /dev/null
+++ b/data/keyboards/Vendor_046d_Product_4011.idc
@@ -0,0 +1,32 @@
+# Copyright 2023 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.
+
+#
+# Logitech Wireless Touchpad
+#
+
+gestureProp.Touchpad_Stack_Version = 1
+gestureProp.IIR_b0 = 1
+gestureProp.IIR_b1 = 0
+gestureProp.IIR_b2 = 0
+gestureProp.IIR_b3 = 0
+gestureProp.IIR_a1 = 0
+gestureProp.IIR_a2 = 0
+gestureProp.Pressure_Calibration_Offset = -313.240741792594
+gestureProp.Pressure_Calibration_Slope = 4.39678062436752
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+gestureProp.Palm_Pressure = 100000.0
+gestureProp.Two_Finger_Vertical_Close_Distance_Thresh = 35.0
+gestureProp.Fling_Buffer_Suppress_Zero_Length_Scrolls = 0
diff --git a/data/keyboards/Vendor_046d_Product_4101.idc b/data/keyboards/Vendor_046d_Product_4101.idc
new file mode 100644
index 0000000..47e2530
--- /dev/null
+++ b/data/keyboards/Vendor_046d_Product_4101.idc
@@ -0,0 +1,31 @@
+# Copyright 2023 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.
+
+#
+# Logitech T650
+#
+
+gestureProp.Touchpad_Stack_Version = 1
+gestureProp.IIR_b0 = 1
+gestureProp.IIR_b1 = 0
+gestureProp.IIR_b2 = 0
+gestureProp.IIR_b3 = 0
+gestureProp.IIR_a1 = 0
+gestureProp.IIR_a2 = 0
+gestureProp.Pressure_Calibration_Offset = -0.439288351750068
+gestureProp.Pressure_Calibration_Slope = 3.05998553523335
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+gestureProp.Two_Finger_Vertical_Close_Distance_Thresh = 35.0
+gestureProp.Fling_Buffer_Suppress_Zero_Length_Scrolls = 0
diff --git a/data/keyboards/Vendor_046d_Product_4102.idc b/data/keyboards/Vendor_046d_Product_4102.idc
new file mode 100644
index 0000000..e33a28a
--- /dev/null
+++ b/data/keyboards/Vendor_046d_Product_4102.idc
@@ -0,0 +1,24 @@
+# Copyright 2023 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.
+
+#
+# Logitech TK820
+#
+
+gestureProp.Touchpad_Stack_Version = 2
+# Pressure jumps around a lot on this touchpad, so allow that:
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+gestureProp.Pressure_Calibration_Offset = -18.8078435
+gestureProp.Pressure_Calibration_Slope = 2.466208137
diff --git a/data/keyboards/Vendor_046d_Product_b00c.idc b/data/keyboards/Vendor_046d_Product_b00c.idc
new file mode 100644
index 0000000..a49970c
--- /dev/null
+++ b/data/keyboards/Vendor_046d_Product_b00c.idc
@@ -0,0 +1,31 @@
+# Copyright 2023 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.
+
+#
+# Logitech T651
+#
+
+gestureProp.Touchpad_Stack_Version = 1
+gestureProp.IIR_b0 = 1
+gestureProp.IIR_b1 = 0
+gestureProp.IIR_b2 = 0
+gestureProp.IIR_b3 = 0
+gestureProp.IIR_a1 = 0
+gestureProp.IIR_a2 = 0
+gestureProp.Pressure_Calibration_Offset = -4.46520447177073
+gestureProp.Pressure_Calibration_Slope = 3.21071719332644
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+gestureProp.Two_Finger_Vertical_Close_Distance_Thresh = 35.0
+gestureProp.Fling_Buffer_Suppress_Zero_Length_Scrolls = 0
diff --git a/data/keyboards/Vendor_05ac_Product_0265.idc b/data/keyboards/Vendor_05ac_Product_0265.idc
new file mode 100644
index 0000000..d72de64
--- /dev/null
+++ b/data/keyboards/Vendor_05ac_Product_0265.idc
@@ -0,0 +1,34 @@
+# Copyright 2023 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.
+
+#
+# Apple Magic Trackpad 2 configuration file
+# Bluetooth vendor ID 004c
+# USB vendor ID 05ac
+#
+
+gestureProp.Pressure_Calibration_Offset = 30
+gestureProp.Palm_Pressure = 250.0
+gestureProp.Palm_Width = 20.0
+gestureProp.Multiple_Palm_Width = 20.0
+
+# Enable Stationary Wiggle Filter
+gestureProp.Stationary_Wiggle_Filter_Enabled = 1
+gestureProp.Finger_Moving_Energy = 0.0008
+gestureProp.Finger_Moving_Hysteresis = 0.0004
+
+# Avoid accidental scroll/move on finger lift
+gestureProp.Max_Stationary_Move_Speed = 47
+gestureProp.Max_Stationary_Move_Speed_Hysteresis = 1
+gestureProp.Max_Stationary_Move_Suppress_Distance = 0.2
diff --git a/data/keyboards/Vendor_05ac_Product_030e.idc b/data/keyboards/Vendor_05ac_Product_030e.idc
new file mode 100644
index 0000000..23a2e18
--- /dev/null
+++ b/data/keyboards/Vendor_05ac_Product_030e.idc
@@ -0,0 +1,38 @@
+# Copyright 2023 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.
+
+#
+# Apple Magic Trackpad
+#
+
+gestureProp.Touchpad_Stack_Version = 1
+# We are using raw touch major value as pressure value, so set the Palm
+# pressure threshold high.
+gestureProp.Palm_Pressure = 1000
+gestureProp.Compute_Surface_Area_from_Pressure = 0
+gestureProp.IIR_b0 = 1
+gestureProp.IIR_b1 = 0
+gestureProp.IIR_b2 = 0
+gestureProp.IIR_b3 = 0
+gestureProp.IIR_a1 = 0
+gestureProp.IIR_a2 = 0
+# NOTE: bias on X-axis is uncalibrated
+gestureProp.Touchpad_Device_Output_Bias_on_X-Axis = -283.3226025266607
+gestureProp.Touchpad_Device_Output_Bias_on_Y-Axis = -283.3226025266607
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+# Drumroll suppression causes janky movement on this touchpad.
+gestureProp.Drumroll_Suppression_Enable = 0
+gestureProp.Two_Finger_Vertical_Close_Distance_Thresh = 35.0
+gestureProp.Fling_Buffer_Suppress_Zero_Length_Scrolls = 0
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 4c669b8..24fea01 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -170,7 +170,7 @@
/**
* Standard CIE 1931 2° illuminant D65, encoded in xyY.
* This illuminant has a color temperature of 6504K. This illuminant
- * is commonly used in RGB color spaces such as sRGB, BT.209, etc.
+ * is commonly used in RGB color spaces such as sRGB, BT.709, etc.
*/
public static final float[] ILLUMINANT_D65 = { 0.31271f, 0.32902f };
/**
@@ -862,8 +862,8 @@
public enum Model {
/**
* The RGB model is a color model with 3 components that
- * refer to the three additive primiaries: red, green
- * andd blue.
+ * refer to the three additive primaries: red, green
+ * and blue.
*/
RGB(3),
/**
@@ -2537,7 +2537,7 @@
* does not need to be specified and is assumed to be 1.0. Only the xy components
* are required.</p>
*
- * <p class="note">The ID, areturned by {@link #getId()}, of an object created by
+ * <p class="note">The ID, as returned by {@link #getId()}, of an object created by
* this constructor is always {@link #MIN_ID}.</p>
*
* @param name Name of the color space, cannot be null, its length must be >= 1
@@ -3961,7 +3961,7 @@
*
* <p>We can only connect color spaces if they use the same profile
* connection space. We assume the connection space is always
- * CIE XYZ but we maye need to perform a chromatic adaptation to
+ * CIE XYZ but we maybe need to perform a chromatic adaptation to
* match the white points. If an adaptation is needed, we use the
* CIE standard illuminant D50. The unmatched color space is adapted
* using the von Kries transform and the {@link Adaptation#BRADFORD}
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index feedb7d..9ac84a6 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -25,13 +25,11 @@
/**
* Gainmap represents a mechanism for augmenting an SDR image to produce an HDR one with variable
- * display adjustment capability.
- *
- * It is a combination of a set of metadata describing how to apply the gainmap, as well as either
- * a 1 (such as {@link android.graphics.Bitmap.Config#ALPHA_8} or 3
+ * display adjustment capability. It is a combination of a set of metadata describing how to apply
+ * the gainmap, as well as either a 1 (such as {@link android.graphics.Bitmap.Config#ALPHA_8} or 3
* (such as {@link android.graphics.Bitmap.Config#ARGB_8888} with the alpha channel ignored)
* channel Bitmap that represents the gainmap data itself.
- *
+ * <p>
* When rendering to an {@link android.content.pm.ActivityInfo#COLOR_MODE_HDR} activity, the
* hardware accelerated {@link Canvas} will automatically apply the gainmap when sufficient
* HDR headroom is available.
@@ -45,7 +43,7 @@
* image, often at a lower resolution (such as 1/4th), along with some metadata to describe
* how to apply the gainmap. The gainmap image itself is then a greyscale image representing
* the transformation to apply onto the base image to reconstruct an HDR rendition of it.
- *
+ * <p>
* As such these "gainmap images" consist of 3 parts - a base {@link Bitmap} with a
* {@link Bitmap#getGainmap()} that returns an instance of this class which in turn contains
* the enhancement layer represented as another Bitmap, accessible via {@link #getGainmapContents()}
@@ -55,25 +53,27 @@
* When doing custom rendering such as to an OpenGL ES or Vulkan context, the gainmap is not
* automatically applied. In such situations, the following steps are appropriate to render the
* gainmap in combination with the base image.
- *
+ * <p>
* Suppose our display has HDR to SDR ratio of H, and we wish to display an image with gainmap on
* this display. Let B be the pixel value from the base image in a color space that has the
* primaries of the base image and a linear transfer function. Let G be the pixel value from the
* gainmap. Let D be the output pixel in the same color space as B. The value of D is computed
* as follows:
- *
+ * <p>
* First, let W be a weight parameter determining how much the gainmap will be applied.
+ * <pre class="prettyprint">
* W = clamp((log(H) - log(minDisplayRatioForHdrTransition)) /
- * (log(displayRatioForFullHdr) - log(minDisplayRatioForHdrTransition), 0, 1)
+ * (log(displayRatioForFullHdr) - log(minDisplayRatioForHdrTransition), 0, 1)</pre>
*
* Next, let L be the gainmap value in log space. We compute this from the value G that was
* sampled from the texture as follows:
- * L = mix(log(ratioMin), log(ratioMax), pow(G, gamma))
- *
+ * <pre class="prettyprint">
+ * L = mix(log(ratioMin), log(ratioMax), pow(G, gamma))</pre>
* Finally, apply the gainmap to compute D, the displayed pixel. If the base image is SDR then
* compute:
- * D = (B + epsilonSdr) * exp(L * W) - epsilonHdr
- *
+ * <pre class="prettyprint">
+ * D = (B + epsilonSdr) * exp(L * W) - epsilonHdr</pre>
+ * <p>
* In the above math, log() is a natural logarithm and exp() is natural exponentiation. The base
* for these functions cancels out and does not affect the result, so other bases may be used
* if preferred.
@@ -150,7 +150,6 @@
/**
* Sets the gainmap ratio min. For single-plane gainmaps, r, g, and b should be the same.
*/
- @NonNull
public void setRatioMin(float r, float g, float b) {
nSetRatioMin(mNativePtr, r, g, b);
}
@@ -169,7 +168,6 @@
/**
* Sets the gainmap ratio max. For single-plane gainmaps, r, g, and b should be the same.
*/
- @NonNull
public void setRatioMax(float r, float g, float b) {
nSetRatioMax(mNativePtr, r, g, b);
}
@@ -188,7 +186,6 @@
/**
* Sets the gainmap gamma. For single-plane gainmaps, r, g, and b should be the same.
*/
- @NonNull
public void setGamma(float r, float g, float b) {
nSetGamma(mNativePtr, r, g, b);
}
@@ -208,7 +205,6 @@
* Sets the sdr epsilon which is used to avoid numerical instability.
* For single-plane gainmaps, r, g, and b should be the same.
*/
- @NonNull
public void setEpsilonSdr(float r, float g, float b) {
nSetEpsilonSdr(mNativePtr, r, g, b);
}
@@ -228,7 +224,6 @@
* Sets the hdr epsilon which is used to avoid numerical instability.
* For single-plane gainmaps, r, g, and b should be the same.
*/
- @NonNull
public void setEpsilonHdr(float r, float g, float b) {
nSetEpsilonHdr(mNativePtr, r, g, b);
}
@@ -248,8 +243,7 @@
* Sets the hdr/sdr ratio at which point the gainmap is fully applied.
* @param max The hdr/sdr ratio at which the gainmap is fully applied. Must be >= 1.0f
*/
- @NonNull
- public void setDisplayRatioForFullHdr(float max) {
+ public void setDisplayRatioForFullHdr(@FloatRange(from = 1.0f) float max) {
if (!Float.isFinite(max) || max < 1f) {
throw new IllegalArgumentException(
"setDisplayRatioForFullHdr must be >= 1.0f, got = " + max);
@@ -269,7 +263,6 @@
* Sets the hdr/sdr ratio below which only the SDR image is displayed.
* @param min The minimum hdr/sdr ratio at which to begin applying the gainmap. Must be >= 1.0f
*/
- @NonNull
public void setMinDisplayRatioForHdrTransition(@FloatRange(from = 1.0f) float min) {
if (!Float.isFinite(min) || min < 1f) {
throw new IllegalArgumentException(
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 caefb2e..404429a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1000,7 +1000,10 @@
Activity findActivityBelow(@NonNull Activity activity) {
Activity activityBelow = null;
final TaskFragmentContainer container = getContainerWithActivity(activity);
- if (container != null) {
+ // Looking for the activity below from the information we already have if the container
+ // only embeds activities of the same process because activities of other processes are not
+ // available in this embedding host process for security concern.
+ if (container != null && !container.hasCrossProcessActivities()) {
final List<Activity> containerActivities = container.collectNonFinishingActivities();
final int index = containerActivities.indexOf(activity);
if (index > 0) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 6c553a8..60be9d1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -153,6 +153,11 @@
Runnable mAppearEmptyTimeout;
/**
+ * Whether this TaskFragment contains activities of another process/package.
+ */
+ private boolean mHasCrossProcessActivities;
+
+ /**
* Creates a container with an existing activity that will be re-parented to it in a window
* container transaction.
* @param pairedPrimaryContainer when it is set, the new container will be add right above it
@@ -418,10 +423,18 @@
mAppearEmptyTimeout = null;
}
+ mHasCrossProcessActivities = false;
mInfo = info;
if (mInfo == null || mInfo.isEmpty()) {
return;
}
+
+ // Contains activities of another process if the activities size is not matched to the
+ // running activity count
+ if (mInfo.getRunningActivityCount() != mInfo.getActivities().size()) {
+ mHasCrossProcessActivities = true;
+ }
+
// Only track the pending Intent when the container is empty.
mPendingAppearedIntent = null;
if (mPendingAppearedActivities.isEmpty()) {
@@ -751,6 +764,11 @@
return mPendingAppearedInRequestedTaskFragmentActivities.contains(activityToken);
}
+ /** Whether contains activities of another process */
+ boolean hasCrossProcessActivities() {
+ return mHasCrossProcessActivities;
+ }
+
/** Gets the parent leaf Task id. */
int getTaskId() {
return mTaskContainer.getTaskId();
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 3d0e1c8..cbdc262 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
@@ -1014,6 +1014,25 @@
}
@Test
+ public void testFindActivityBelow() {
+ // Create a container with two activities
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+ final Activity pendingAppearedActivity = createMockActivity();
+ container.addPendingAppearedActivity(pendingAppearedActivity);
+
+ // Ensure the activity below matches
+ assertEquals(mActivity,
+ mSplitController.findActivityBelow(pendingAppearedActivity));
+
+ // Ensure that the activity look up won't search for the in-process activities and should
+ // IPC to WM core to get the activity below. It should be `null` for this mock test.
+ spyOn(container);
+ doReturn(true).when(container).hasCrossProcessActivities();
+ assertNotEquals(mActivity,
+ mSplitController.findActivityBelow(pendingAppearedActivity));
+ }
+
+ @Test
public void testResolveActivityToContainer_inUnknownTaskFragment() {
doReturn(new Binder()).when(mSplitController)
.getTaskFragmentTokenFromActivityClientRecord(mActivity);
diff --git a/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml b/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
index 2dba37d..4c28e51 100644
--- a/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
@@ -17,5 +17,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/tv_window_menu_button_radius" />
- <solid android:color="@color/tv_window_menu_icon_bg" />
+ <solid android:color="@android:color/white" />
</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 585f81c..b6fd0bb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -428,9 +428,9 @@
}
@Override
- public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
+ public void addStartingWindow(StartingWindowInfo info) {
if (mStartingWindow != null) {
- mStartingWindow.addStartingWindow(info, appToken);
+ mStartingWindow.addStartingWindow(info);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 0b87598..349ff36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -120,6 +120,9 @@
@Nullable
private IOnBackInvokedCallback mActiveCallback;
+ private CrossActivityAnimation mDefaultActivityAnimation;
+ private CustomizeActivityAnimation mCustomizeActivityAnimation;
+
@VisibleForTesting
final RemoteCallback mNavigationObserver = new RemoteCallback(
new RemoteCallback.OnResultListener() {
@@ -194,10 +197,12 @@
new CrossTaskBackAnimation(mContext, mAnimationBackground);
mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
crossTaskAnimation.mBackAnimationRunner);
- final CrossActivityAnimation crossActivityAnimation =
+ mDefaultActivityAnimation =
new CrossActivityAnimation(mContext, mAnimationBackground);
mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
- crossActivityAnimation.mBackAnimationRunner);
+ mDefaultActivityAnimation.mBackAnimationRunner);
+ mCustomizeActivityAnimation =
+ new CustomizeActivityAnimation(mContext, mAnimationBackground);
// TODO (236760237): register dialog close animation when it's completed.
}
@@ -368,7 +373,6 @@
final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
if (shouldDispatchToAnimator) {
if (mAnimationDefinition.contains(backType)) {
- mActiveCallback = mAnimationDefinition.get(backType).getCallback();
mAnimationDefinition.get(backType).startGesture();
} else {
mActiveCallback = null;
@@ -542,13 +546,12 @@
}
final int backType = mBackNavigationInfo.getType();
+ final BackAnimationRunner runner = mAnimationDefinition.get(backType);
// Simply trigger and finish back navigation when no animator defined.
- if (!shouldDispatchToAnimator() || mActiveCallback == null) {
+ if (!shouldDispatchToAnimator() || runner == null) {
invokeOrCancelBack();
return;
}
-
- final BackAnimationRunner runner = mAnimationDefinition.get(backType);
if (runner.isWaitingAnimation()) {
ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready.");
return;
@@ -607,6 +610,12 @@
mShouldStartOnNextMoveEvent = false;
mTouchTracker.reset();
mActiveCallback = null;
+ // reset to default
+ if (mDefaultActivityAnimation != null
+ && mAnimationDefinition.contains(BackNavigationInfo.TYPE_CROSS_ACTIVITY)) {
+ mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ mDefaultActivityAnimation.mBackAnimationRunner);
+ }
if (mBackNavigationInfo != null) {
mBackNavigationInfo.onBackNavigationFinished(mTriggerBack);
mBackNavigationInfo = null;
@@ -614,14 +623,35 @@
mTriggerBack = false;
}
+ private BackAnimationRunner getAnimationRunnerAndInit() {
+ int type = mBackNavigationInfo.getType();
+ // Initiate customized cross-activity animation, or fall back to cross activity animation
+ if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) {
+ final BackNavigationInfo.CustomAnimationInfo animationInfo =
+ mBackNavigationInfo.getCustomAnimationInfo();
+ if (animationInfo != null && mCustomizeActivityAnimation != null
+ && mCustomizeActivityAnimation.prepareNextAnimation(animationInfo)) {
+ mAnimationDefinition.get(type).resetWaitingAnimation();
+ mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ mCustomizeActivityAnimation.mBackAnimationRunner);
+ }
+ }
+ return mAnimationDefinition.get(type);
+ }
+
private void createAdapter() {
IBackAnimationRunner runner = new IBackAnimationRunner.Stub() {
@Override
- public void onAnimationStart(int type, RemoteAnimationTarget[] apps,
+ public void onAnimationStart(RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IBackAnimationFinishedCallback finishedCallback) {
mShellExecutor.execute(() -> {
- final BackAnimationRunner runner = mAnimationDefinition.get(type);
+ if (mBackNavigationInfo == null) {
+ Log.e(TAG, "Lack of navigation info to start animation.");
+ return;
+ }
+ final int type = mBackNavigationInfo.getType();
+ final BackAnimationRunner runner = getAnimationRunnerAndInit();
if (runner == null) {
Log.e(TAG, "Animation didn't be defined for type "
+ BackNavigationInfo.typeToString(type));
@@ -634,6 +664,7 @@
}
return;
}
+ mActiveCallback = runner.getCallback();
mBackAnimationFinishedCallback = finishedCallback;
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
@@ -645,11 +676,13 @@
mActiveCallback, mTouchTracker.createStartEvent(apps[0]));
}
+ // Dispatch the first progress after animation start for smoothing the initial
+ // animation, instead of waiting for next onMove.
+ final BackMotionEvent backFinish = mTouchTracker.createProgressEvent();
+ dispatchOnBackProgressed(mActiveCallback, backFinish);
if (!mBackGestureStarted) {
// if the down -> up gesture happened before animation start, we have to
// trigger the uninterruptible transition to finish the back animation.
- final BackMotionEvent backFinish = mTouchTracker.createProgressEvent();
- dispatchOnBackProgressed(mActiveCallback, backFinish);
startPostCommitAnimation();
}
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 82c523f..22b841a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -99,4 +99,8 @@
boolean isAnimationCancelled() {
return mAnimationCancelled;
}
+
+ void resetWaitingAnimation() {
+ mWaitingAnimation = false;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
new file mode 100644
index 0000000..ae33b94
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2023 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.back;
+
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.FloatProperty;
+import android.view.Choreographer;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Transformation;
+import android.window.BackEvent;
+import android.window.BackMotionEvent;
+import android.window.BackNavigationInfo;
+import android.window.BackProgressAnimator;
+import android.window.IOnBackInvokedCallback;
+
+import com.android.internal.dynamicanimation.animation.SpringAnimation;
+import com.android.internal.dynamicanimation.animation.SpringForce;
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.policy.TransitionAnimation;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/**
+ * Class that handle customized close activity transition animation.
+ */
+@ShellMainThread
+class CustomizeActivityAnimation {
+ private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+ final BackAnimationRunner mBackAnimationRunner;
+ private final float mCornerRadius;
+ private final SurfaceControl.Transaction mTransaction;
+ private final BackAnimationBackground mBackground;
+ private RemoteAnimationTarget mEnteringTarget;
+ private RemoteAnimationTarget mClosingTarget;
+ private IRemoteAnimationFinishedCallback mFinishCallback;
+ /** Duration of post animation after gesture committed. */
+ private static final int POST_ANIMATION_DURATION = 250;
+
+ private static final int SCALE_FACTOR = 1000;
+ private final SpringAnimation mProgressSpring;
+ private float mLatestProgress = 0.0f;
+
+ private static final float TARGET_COMMIT_PROGRESS = 0.5f;
+
+ private final float[] mTmpFloat9 = new float[9];
+ private final DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
+
+ final CustomAnimationLoader mCustomAnimationLoader;
+ private Animation mEnterAnimation;
+ private Animation mCloseAnimation;
+ final Transformation mTransformation = new Transformation();
+
+ private final Choreographer mChoreographer;
+
+ CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
+ this(context, background, new SurfaceControl.Transaction(), null);
+ }
+
+ CustomizeActivityAnimation(Context context, BackAnimationBackground background,
+ SurfaceControl.Transaction transaction, Choreographer choreographer) {
+ mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mBackground = background;
+ mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mCustomAnimationLoader = new CustomAnimationLoader(context);
+
+ mProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
+ mProgressSpring.setSpring(new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
+ mTransaction = transaction == null ? new SurfaceControl.Transaction() : transaction;
+ mChoreographer = choreographer != null ? choreographer : Choreographer.getInstance();
+ }
+
+ private float getLatestProgress() {
+ return mLatestProgress * SCALE_FACTOR;
+ }
+ private void setLatestProgress(float value) {
+ mLatestProgress = value / SCALE_FACTOR;
+ applyTransformTransaction(mLatestProgress);
+ }
+
+ private static final FloatProperty<CustomizeActivityAnimation> ENTER_PROGRESS_PROP =
+ new FloatProperty<>("enter") {
+ @Override
+ public void setValue(CustomizeActivityAnimation anim, float value) {
+ anim.setLatestProgress(value);
+ }
+
+ @Override
+ public Float get(CustomizeActivityAnimation object) {
+ return object.getLatestProgress();
+ }
+ };
+
+ // The target will lose focus when alpha == 0, so keep a minimum value for it.
+ private static float keepMinimumAlpha(float transAlpha) {
+ return Math.max(transAlpha, 0.005f);
+ }
+
+ private static void initializeAnimation(Animation animation, Rect bounds) {
+ final int width = bounds.width();
+ final int height = bounds.height();
+ animation.initialize(width, height, width, height);
+ }
+
+ private void startBackAnimation() {
+ if (mEnteringTarget == null || mClosingTarget == null
+ || mCloseAnimation == null || mEnterAnimation == null) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
+ return;
+ }
+ initializeAnimation(mCloseAnimation, mClosingTarget.localBounds);
+ initializeAnimation(mEnterAnimation, mEnteringTarget.localBounds);
+
+ // Draw background with task background color.
+ if (mEnteringTarget.taskInfo != null && mEnteringTarget.taskInfo.taskDescription != null) {
+ mBackground.ensureBackground(
+ mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction);
+ }
+ }
+
+ private void applyTransformTransaction(float progress) {
+ if (mClosingTarget == null || mEnteringTarget == null) {
+ return;
+ }
+ applyTransform(mClosingTarget.leash, progress, mCloseAnimation);
+ applyTransform(mEnteringTarget.leash, progress, mEnterAnimation);
+ mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
+ mTransaction.apply();
+ }
+
+ private void applyTransform(SurfaceControl leash, float progress, Animation animation) {
+ mTransformation.clear();
+ animation.getTransformationAt(progress, mTransformation);
+ mTransaction.setMatrix(leash, mTransformation.getMatrix(), mTmpFloat9);
+ mTransaction.setAlpha(leash, keepMinimumAlpha(mTransformation.getAlpha()));
+ mTransaction.setCornerRadius(leash, mCornerRadius);
+ }
+
+ void finishAnimation() {
+ if (mCloseAnimation != null) {
+ mCloseAnimation.reset();
+ mCloseAnimation = null;
+ }
+ if (mEnterAnimation != null) {
+ mEnterAnimation.reset();
+ mEnterAnimation = null;
+ }
+ if (mEnteringTarget != null) {
+ mEnteringTarget.leash.release();
+ mEnteringTarget = null;
+ }
+ if (mClosingTarget != null) {
+ mClosingTarget.leash.release();
+ mClosingTarget = null;
+ }
+ if (mBackground != null) {
+ mBackground.removeBackground(mTransaction);
+ }
+ mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
+ mTransaction.apply();
+ mTransformation.clear();
+ mLatestProgress = 0;
+ if (mFinishCallback != null) {
+ try {
+ mFinishCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ mFinishCallback = null;
+ }
+ mProgressSpring.animateToFinalPosition(0);
+ mProgressSpring.skipToEnd();
+ }
+
+ void onGestureProgress(@NonNull BackEvent backEvent) {
+ if (mEnteringTarget == null || mClosingTarget == null
+ || mCloseAnimation == null || mEnterAnimation == null) {
+ return;
+ }
+
+ final float progress = backEvent.getProgress();
+
+ float springProgress = (progress > 0.1f
+ ? mapLinear(progress, 0.1f, 1f, TARGET_COMMIT_PROGRESS, 1f)
+ : mapLinear(progress, 0, 1f, 0f, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR;
+
+ mProgressSpring.animateToFinalPosition(springProgress);
+ }
+
+ static float mapLinear(float x, float a1, float a2, float b1, float b2) {
+ return b1 + (x - a1) * (b2 - b1) / (a2 - a1);
+ }
+
+ void onGestureCommitted() {
+ if (mEnteringTarget == null || mClosingTarget == null
+ || mCloseAnimation == null || mEnterAnimation == null) {
+ finishAnimation();
+ return;
+ }
+ mProgressSpring.cancel();
+
+ // Enter phase 2 of the animation
+ final ValueAnimator valueAnimator = ValueAnimator.ofFloat(mLatestProgress, 1f)
+ .setDuration(POST_ANIMATION_DURATION);
+ valueAnimator.setInterpolator(mDecelerateInterpolator);
+ valueAnimator.addUpdateListener(animation -> {
+ float progress = (float) animation.getAnimatedValue();
+ applyTransformTransaction(progress);
+ });
+
+ valueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finishAnimation();
+ }
+ });
+ valueAnimator.start();
+ }
+
+ /**
+ * Load customize animation before animation start.
+ */
+ boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+ mCloseAnimation = mCustomAnimationLoader.load(
+ animationInfo, false /* enterAnimation */);
+ if (mCloseAnimation != null) {
+ mEnterAnimation = mCustomAnimationLoader.load(
+ animationInfo, true /* enterAnimation */);
+ return true;
+ }
+ return false;
+ }
+
+ private final class Callback extends IOnBackInvokedCallback.Default {
+ @Override
+ public void onBackStarted(BackMotionEvent backEvent) {
+ mProgressAnimator.onBackStarted(backEvent,
+ CustomizeActivityAnimation.this::onGestureProgress);
+ }
+
+ @Override
+ public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
+ mProgressAnimator.onBackProgressed(backEvent);
+ }
+
+ @Override
+ public void onBackCancelled() {
+ mProgressAnimator.onBackCancelled(CustomizeActivityAnimation.this::finishAnimation);
+ }
+
+ @Override
+ public void onBackInvoked() {
+ mProgressAnimator.reset();
+ onGestureCommitted();
+ }
+ }
+
+ private final class Runner extends IRemoteAnimationRunner.Default {
+ @Override
+ public void onAnimationStart(
+ int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to customize animation.");
+ for (RemoteAnimationTarget a : apps) {
+ if (a.mode == MODE_CLOSING) {
+ mClosingTarget = a;
+ }
+ if (a.mode == MODE_OPENING) {
+ mEnteringTarget = a;
+ }
+ }
+ if (mCloseAnimation == null || mEnterAnimation == null) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW,
+ "No animation loaded, should choose cross-activity animation?");
+ }
+
+ startBackAnimation();
+ mFinishCallback = finishedCallback;
+ }
+
+ @Override
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
+ finishAnimation();
+ }
+ }
+
+ /**
+ * Helper class to load custom animation.
+ */
+ static class CustomAnimationLoader {
+ private final TransitionAnimation mTransitionAnimation;
+
+ CustomAnimationLoader(Context context) {
+ mTransitionAnimation = new TransitionAnimation(
+ context, false /* debug */, "CustomizeBackAnimation");
+ }
+
+ Animation load(BackNavigationInfo.CustomAnimationInfo animationInfo,
+ boolean enterAnimation) {
+ final String packageName = animationInfo.getPackageName();
+ if (packageName.isEmpty()) {
+ return null;
+ }
+ final int windowAnimations = animationInfo.getWindowAnimations();
+ if (windowAnimations == 0) {
+ return null;
+ }
+ final int attrs = enterAnimation
+ ? com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation
+ : com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
+ Animation a = mTransitionAnimation.loadAnimationAttr(packageName, windowAnimations,
+ attrs, false /* translucent */);
+ // Only allow to load default animation for opening target.
+ if (a == null && enterAnimation) {
+ a = mTransitionAnimation.loadDefaultAnimationAttr(attrs, false /* translucent */);
+ }
+ if (a != null) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "custom animation loaded %s", a);
+ } else {
+ ProtoLog.e(WM_SHELL_BACK_PREVIEW, "No custom animation loaded");
+ }
+ return a;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
index 22587f4..8b4ac1a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
@@ -39,6 +39,9 @@
*
* Note that most of the implementation here inherits from
* {@link com.android.systemui.statusbar.policy.DevicePostureController}.
+ *
+ * Use the {@link TabletopModeController} if you are interested in tabletop mode change only,
+ * which is more common.
*/
public class DevicePostureController {
@IntDef(prefix = {"DEVICE_POSTURE_"}, value = {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
new file mode 100644
index 0000000..bf226283
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2023 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.common;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FOLDABLE;
+
+import android.annotation.NonNull;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.ArraySet;
+import android.view.Surface;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Wrapper class to track the tabletop (aka. flex) mode change on Fold-ables.
+ * See also <a
+ * href="https://developer.android.com/guide/topics/large-screens/learn-about-foldables
+ * #foldable_postures">Foldable states and postures</a> for reference.
+ *
+ * Use the {@link DevicePostureController} for more detailed posture changes.
+ */
+public class TabletopModeController implements
+ DevicePostureController.OnDevicePostureChangedListener,
+ DisplayController.OnDisplaysChangedListener {
+ private static final long TABLETOP_MODE_DELAY_MILLIS = 1_000;
+
+ private final Context mContext;
+
+ private final DevicePostureController mDevicePostureController;
+
+ private final DisplayController mDisplayController;
+
+ private final ShellExecutor mMainExecutor;
+
+ private final Set<Integer> mTabletopModeRotations = new ArraySet<>();
+
+ private final List<OnTabletopModeChangedListener> mListeners = new ArrayList<>();
+
+ @VisibleForTesting
+ final Runnable mOnEnterTabletopModeCallback = () -> {
+ if (isInTabletopMode()) {
+ // We are still in tabletop mode, go ahead.
+ mayBroadcastOnTabletopModeChange(true /* isInTabletopMode */);
+ }
+ };
+
+ @DevicePostureController.DevicePostureInt
+ private int mDevicePosture = DEVICE_POSTURE_UNKNOWN;
+
+ @Surface.Rotation
+ private int mDisplayRotation = WindowConfiguration.ROTATION_UNDEFINED;
+
+ /**
+ * Track the last callback value for {@link OnTabletopModeChangedListener}.
+ * This is to avoid duplicated {@code false} callback to {@link #mListeners}.
+ */
+ private Boolean mLastIsInTabletopModeForCallback;
+
+ public TabletopModeController(Context context,
+ ShellInit shellInit,
+ DevicePostureController postureController,
+ DisplayController displayController,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ mContext = context;
+ mDevicePostureController = postureController;
+ mDisplayController = displayController;
+ mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ @VisibleForTesting
+ void onInit() {
+ mDevicePostureController.registerOnDevicePostureChangedListener(this);
+ mDisplayController.addDisplayWindowListener(this);
+ // Aligns with what's in {@link com.android.server.wm.DisplayRotation}.
+ final int[] deviceTabletopRotations = mContext.getResources().getIntArray(
+ com.android.internal.R.array.config_deviceTabletopRotations);
+ if (deviceTabletopRotations == null || deviceTabletopRotations.length == 0) {
+ ProtoLog.e(WM_SHELL_FOLDABLE,
+ "No valid config_deviceTabletopRotations, can not tell"
+ + " tabletop mode in WMShell");
+ return;
+ }
+ for (int angle : deviceTabletopRotations) {
+ switch (angle) {
+ case 0:
+ mTabletopModeRotations.add(Surface.ROTATION_0);
+ break;
+ case 90:
+ mTabletopModeRotations.add(Surface.ROTATION_90);
+ break;
+ case 180:
+ mTabletopModeRotations.add(Surface.ROTATION_180);
+ break;
+ case 270:
+ mTabletopModeRotations.add(Surface.ROTATION_270);
+ break;
+ default:
+ ProtoLog.e(WM_SHELL_FOLDABLE,
+ "Invalid surface rotation angle in "
+ + "config_deviceTabletopRotations: %d",
+ angle);
+ break;
+ }
+ }
+ }
+
+ /** Register {@link OnTabletopModeChangedListener} to listen for tabletop mode change. */
+ public void registerOnTabletopModeChangedListener(
+ @NonNull OnTabletopModeChangedListener listener) {
+ if (listener == null || mListeners.contains(listener)) return;
+ mListeners.add(listener);
+ listener.onTabletopModeChanged(isInTabletopMode());
+ }
+
+ /** Unregister {@link OnTabletopModeChangedListener} for tabletop mode change. */
+ public void unregisterOnTabletopModeChangedListener(
+ @NonNull OnTabletopModeChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public void onDevicePostureChanged(@DevicePostureController.DevicePostureInt int posture) {
+ if (mDevicePosture != posture) {
+ onDevicePostureOrDisplayRotationChanged(posture, mDisplayRotation);
+ }
+ }
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ final int newDisplayRotation = newConfig.windowConfiguration.getDisplayRotation();
+ if (displayId == DEFAULT_DISPLAY && newDisplayRotation != mDisplayRotation) {
+ onDevicePostureOrDisplayRotationChanged(mDevicePosture, newDisplayRotation);
+ }
+ }
+
+ private void onDevicePostureOrDisplayRotationChanged(
+ @DevicePostureController.DevicePostureInt int newPosture,
+ @Surface.Rotation int newDisplayRotation) {
+ final boolean wasInTabletopMode = isInTabletopMode();
+ mDevicePosture = newPosture;
+ mDisplayRotation = newDisplayRotation;
+ final boolean couldBeInTabletopMode = isInTabletopMode();
+ mMainExecutor.removeCallbacks(mOnEnterTabletopModeCallback);
+ if (!wasInTabletopMode && couldBeInTabletopMode) {
+ // May enter tabletop mode, but we need to wait for additional time since this
+ // could be an intermediate state.
+ mMainExecutor.executeDelayed(mOnEnterTabletopModeCallback, TABLETOP_MODE_DELAY_MILLIS);
+ } else {
+ // Cancel entering tabletop mode if any condition's changed.
+ mayBroadcastOnTabletopModeChange(false /* isInTabletopMode */);
+ }
+ }
+
+ private boolean isHalfOpened(@DevicePostureController.DevicePostureInt int posture) {
+ return posture == DEVICE_POSTURE_HALF_OPENED;
+ }
+
+ private boolean isInTabletopMode() {
+ return isHalfOpened(mDevicePosture) && mTabletopModeRotations.contains(mDisplayRotation);
+ }
+
+ private void mayBroadcastOnTabletopModeChange(boolean isInTabletopMode) {
+ if (mLastIsInTabletopModeForCallback == null
+ || mLastIsInTabletopModeForCallback != isInTabletopMode) {
+ mListeners.forEach(l -> l.onTabletopModeChanged(isInTabletopMode));
+ mLastIsInTabletopModeForCallback = isInTabletopMode;
+ }
+ }
+
+ /**
+ * Listener interface for tabletop mode change.
+ */
+ public interface OnTabletopModeChangedListener {
+ /**
+ * Callback when tabletop mode changes. Expect duplicated callbacks with {@code false}.
+ * @param isInTabletopMode {@code true} if enters tabletop mode, {@code false} otherwise.
+ */
+ void onTabletopModeChanged(boolean isInTabletopMode);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
index 8ba785a..931cf0c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
@@ -70,6 +70,8 @@
setTextAndDescription(textResId);
}
typedArray.recycle();
+
+ setIsCustomCloseAction(false);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index e44257e..4970fa0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -248,11 +248,11 @@
/** Stops showing resizing hint. */
public void onResized(SurfaceControl.Transaction t, Runnable animFinishedCallback) {
- if (mScreenshot != null) {
- if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
- mScreenshotAnimator.cancel();
- }
+ if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ }
+ if (mScreenshot != null) {
t.setPosition(mScreenshot, mOffsetX, mOffsetY);
final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
@@ -322,6 +322,10 @@
/** Screenshot host leash and attach on it if meet some conditions */
public void screenshotIfNeeded(SurfaceControl.Transaction t) {
if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+ if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ }
+
mTempRect.set(mOldBounds);
mTempRect.offsetTo(0, 0);
mScreenshot = ScreenshotUtils.takeScreenshot(t, mHostLeash, mTempRect,
@@ -334,6 +338,10 @@
if (screenshot == null || !screenshot.isValid()) return;
if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+ if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ }
+
mScreenshot = screenshot;
t.reparent(screenshot, mHostLeash);
t.setLayer(screenshot, Integer.MAX_VALUE - 1);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 8380225..3d5230d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -52,6 +52,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellAnimationThread;
@@ -172,6 +173,18 @@
@WMSingleton
@Provides
+ static TabletopModeController provideTabletopModeController(
+ Context context,
+ ShellInit shellInit,
+ DevicePostureController postureController,
+ DisplayController displayController,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new TabletopModeController(
+ context, shellInit, postureController, displayController, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
static DragAndDropController provideDragAndDropController(Context context,
ShellInit shellInit,
ShellController shellController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index 480bf93..53bf42a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -39,6 +39,9 @@
* Represents the content overlay used during the entering PiP animation.
*/
public abstract class PipContentOverlay {
+ // Fixed string used in WMShellFlickerTests
+ protected static final String LAYER_NAME = "PipContentOverlay";
+
protected SurfaceControl mLeash;
/** Attaches the internal {@link #mLeash} to the given parent leash. */
@@ -86,7 +89,7 @@
mContext = context;
mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite(TAG)
- .setName(TAG)
+ .setName(LAYER_NAME)
.setColorLayer()
.build();
}
@@ -139,7 +142,7 @@
mSourceRectHint = new Rect(sourceRectHint);
mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite(TAG)
- .setName(TAG)
+ .setName(LAYER_NAME)
.build();
}
@@ -194,7 +197,7 @@
prepareAppIconOverlay(activityInfo);
mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite(TAG)
- .setName(TAG)
+ .setName(LAYER_NAME)
.build();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index f11836e..e9d2571 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1594,7 +1594,7 @@
// source rect hint to enter PiP use bounds animation.
if (sourceHintRect == null) {
if (SystemProperties.getBoolean(
- "persist.wm.debug.enable_pip_app_icon_overlay", false)) {
+ "persist.wm.debug.enable_pip_app_icon_overlay", true)) {
animator.setAppIconContentOverlay(
mContext, currentBounds, mTaskInfo.topActivityInfo);
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 6b0337d..a91a342 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -804,7 +804,7 @@
// We use content overlay when there is no source rect hint to enter PiP use bounds
// animation.
if (SystemProperties.getBoolean(
- "persist.wm.debug.enable_pip_app_icon_overlay", false)) {
+ "persist.wm.debug.enable_pip_app_icon_overlay", true)) {
animator.setAppIconContentOverlay(
mContext, currentBounds, taskInfo.topActivityInfo);
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
index 449a2bf..49d40d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
@@ -60,6 +60,7 @@
}
button.setImageIconAsync(mRemoteAction.getIcon(), mainHandler);
button.setEnabled(isCloseAction() || mRemoteAction.isEnabled());
+ button.setIsCustomCloseAction(isCloseAction());
}
PendingIntent getPendingIntent() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
index 93b6a90..4b82e4b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
@@ -61,6 +61,7 @@
button.setTextAndDescription(mTitleResource);
button.setImageResource(mIconResource);
button.setEnabled(true);
+ button.setIsCustomCloseAction(false);
}
PendingIntent getPendingIntent() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 75f9a4c..c9b3a1a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -50,6 +50,8 @@
Consts.TAG_WM_SHELL),
WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
+ WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_SHELL),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
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 70d3b35..18a3849 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
@@ -764,17 +764,9 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (pendingIntent2 == null) {
- // Launching a solo task.
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
- options1 = activityOptions.toBundle();
- addActivityOptions(options1, null /* launchTarget */);
- if (shortcutInfo1 != null) {
- wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
- } else {
- wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
- }
- mSyncQueue.queue(wct);
+ // Launching a solo intent or shortcut as fullscreen.
+ launchAsFullscreenWithRemoteAnimation(pendingIntent1, fillInIntent1, shortcutInfo1,
+ options1, adapter, wct);
return;
}
@@ -797,13 +789,9 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (taskId == INVALID_TASK_ID) {
- // Launching a solo task.
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
- options1 = activityOptions.toBundle();
- addActivityOptions(options1, null /* launchTarget */);
- wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
- mSyncQueue.queue(wct);
+ // Launching a solo intent as fullscreen.
+ launchAsFullscreenWithRemoteAnimation(pendingIntent, fillInIntent, null, options1,
+ adapter, wct);
return;
}
@@ -822,13 +810,8 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (taskId == INVALID_TASK_ID) {
- // Launching a solo task.
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
- options1 = activityOptions.toBundle();
- addActivityOptions(options1, null /* launchTarget */);
- wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
- mSyncQueue.queue(wct);
+ // Launching a solo shortcut as fullscreen.
+ launchAsFullscreenWithRemoteAnimation(null, null, shortcutInfo, options1, adapter, wct);
return;
}
@@ -838,6 +821,49 @@
instanceId);
}
+ private void launchAsFullscreenWithRemoteAnimation(@Nullable PendingIntent pendingIntent,
+ @Nullable Intent fillInIntent, @Nullable ShortcutInfo shortcutInfo,
+ @Nullable Bundle options, RemoteAnimationAdapter adapter,
+ WindowContainerTransaction wct) {
+ LegacyTransitions.ILegacyTransition transition =
+ (transit, apps, wallpapers, nonApps, finishedCallback, t) -> {
+ if (apps == null || apps.length == 0) {
+ onRemoteAnimationFinished(apps);
+ t.apply();
+ try {
+ adapter.getRunner().onAnimationCancelled(mKeyguardShowing);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ return;
+ }
+
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
+ }
+ t.apply();
+
+ try {
+ adapter.getRunner().onAnimationStart(
+ transit, apps, wallpapers, nonApps, finishedCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ };
+
+ addActivityOptions(options, null /* launchTarget */);
+ if (shortcutInfo != null) {
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo, options);
+ } else if (pendingIntent != null) {
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options);
+ } else {
+ Slog.e(TAG, "Pending intent and shortcut are null is invalid case.");
+ }
+ mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
+ }
+
private void startWithLegacyTransition(WindowContainerTransaction wct,
@Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
@Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions,
@@ -894,23 +920,25 @@
if (options == null) options = new Bundle();
addActivityOptions(options, mMainStage);
- options = wrapAsSplitRemoteAnimation(adapter, options);
updateWindowBounds(mSplitLayout, wct);
-
- // TODO(b/268008375): Merge APIs to start a split pair into one.
- if (mainTaskId != INVALID_TASK_ID) {
- wct.startTask(mainTaskId, options);
- } else if (mainShortcutInfo != null) {
- wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options);
- } else {
- wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options);
- }
-
wct.reorder(mRootTaskInfo.token, true);
wct.setForceTranslucent(mRootTaskInfo.token, false);
- mSyncQueue.queue(wct);
+ // TODO(b/268008375): Merge APIs to start a split pair into one.
+ if (mainTaskId != INVALID_TASK_ID) {
+ options = wrapAsSplitRemoteAnimation(adapter, options);
+ wct.startTask(mainTaskId, options);
+ mSyncQueue.queue(wct);
+ } else {
+ if (mainShortcutInfo != null) {
+ wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options);
+ } else {
+ wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options);
+ }
+ mSyncQueue.queue(wrapAsSplitRemoteAnimation(adapter), WindowManager.TRANSIT_OPEN, wct);
+ }
+
mSyncQueue.runInSync(t -> {
setDividerVisibility(true, t);
});
@@ -967,6 +995,54 @@
return activityOptions.toBundle();
}
+ private LegacyTransitions.ILegacyTransition wrapAsSplitRemoteAnimation(
+ RemoteAnimationAdapter adapter) {
+ LegacyTransitions.ILegacyTransition transition =
+ (transit, apps, wallpapers, nonApps, finishedCallback, t) -> {
+ if (apps == null || apps.length == 0) {
+ onRemoteAnimationFinished(apps);
+ t.apply();
+ try {
+ adapter.getRunner().onAnimationCancelled(mKeyguardShowing);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ return;
+ }
+
+ // Wrap the divider bar into non-apps target to animate together.
+ nonApps = ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps,
+ getDividerBarLegacyTarget());
+
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ // Reset the surface position of the opening app to prevent offset.
+ t.setPosition(apps[i].leash, 0, 0);
+ }
+ }
+ t.apply();
+
+ IRemoteAnimationFinishedCallback wrapCallback =
+ new IRemoteAnimationFinishedCallback.Stub() {
+ @Override
+ public void onAnimationFinished() throws RemoteException {
+ onRemoteAnimationFinished(apps);
+ finishedCallback.onAnimationFinished();
+ }
+ };
+ Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication());
+ try {
+ adapter.getRunner().onAnimationStart(
+ transit, apps, wallpapers, nonApps, wrapCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ };
+
+ return transition;
+ }
+
private void setEnterInstanceId(InstanceId instanceId) {
if (instanceId != null) {
mLogger.enterRequested(instanceId, ENTER_REASON_LAUNCHER);
@@ -993,6 +1069,27 @@
}
}
+ private void onRemoteAnimationFinished(RemoteAnimationTarget[] apps) {
+ mIsDividerRemoteAnimating = false;
+ mShouldUpdateRecents = true;
+ mSplitRequest = null;
+ // If any stage has no child after finished animation, that side of the split will display
+ // nothing. This might happen if starting the same app on the both sides while not
+ // supporting multi-instance. Exit the split screen and expand that app to full screen.
+ if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
+ mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0
+ ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ mSplitUnsupportedToast.show();
+ return;
+ }
+
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ prepareEvictNonOpeningChildTasks(SPLIT_POSITION_TOP_OR_LEFT, apps, evictWct);
+ prepareEvictNonOpeningChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, apps, evictWct);
+ mSyncQueue.queue(evictWct);
+ }
+
+
/**
* Collects all the current child tasks of a specific split and prepares transaction to evict
* them to display.
@@ -1704,7 +1801,9 @@
// Split entering background.
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
true /* setReparentLeafTaskIfRelaunch */);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
+ if (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping) {
+ wct.setForceTranslucent(mRootTaskInfo.token, true);
+ }
} else {
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
false /* setReparentLeafTaskIfRelaunch */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java
new file mode 100644
index 0000000..1ddd8f9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 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.startingsurface;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+
+import com.android.wm.shell.common.ShellExecutor;
+
+// abstract class to create splash screen window(or windowless window)
+abstract class AbsSplashWindowCreator {
+ protected static final String TAG = StartingWindowController.TAG;
+ protected final SplashscreenContentDrawer mSplashscreenContentDrawer;
+ protected final Context mContext;
+ protected final DisplayManager mDisplayManager;
+ protected final ShellExecutor mSplashScreenExecutor;
+ protected final StartingSurfaceDrawer.StartingWindowRecordManager mStartingWindowRecordManager;
+
+ private StartingSurface.SysuiProxy mSysuiProxy;
+
+ AbsSplashWindowCreator(SplashscreenContentDrawer contentDrawer, Context context,
+ ShellExecutor splashScreenExecutor, DisplayManager displayManager,
+ StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager) {
+ mSplashscreenContentDrawer = contentDrawer;
+ mContext = context;
+ mSplashScreenExecutor = splashScreenExecutor;
+ mDisplayManager = displayManager;
+ mStartingWindowRecordManager = startingWindowRecordManager;
+ }
+
+ int getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo) {
+ return splashScreenThemeResId != 0
+ ? splashScreenThemeResId
+ : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource()
+ : com.android.internal.R.style.Theme_DeviceDefault_DayNight;
+ }
+
+ protected Display getDisplay(int displayId) {
+ return mDisplayManager.getDisplay(displayId);
+ }
+
+ void setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy) {
+ mSysuiProxy = sysuiProxy;
+ }
+
+ protected void requestTopUi(boolean requestTopUi) {
+ if (mSysuiProxy != null) {
+ mSysuiProxy.requestTopUi(requestTopUi, TAG);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java
new file mode 100644
index 0000000..20c4d5a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 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.startingsurface;
+
+import android.window.StartingWindowInfo;
+import android.window.TaskSnapshot;
+
+import com.android.wm.shell.common.ShellExecutor;
+
+class SnapshotWindowCreator {
+ private final ShellExecutor mMainExecutor;
+ private final StartingSurfaceDrawer.StartingWindowRecordManager
+ mStartingWindowRecordManager;
+
+ SnapshotWindowCreator(ShellExecutor mainExecutor,
+ StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager) {
+ mMainExecutor = mainExecutor;
+ mStartingWindowRecordManager = startingWindowRecordManager;
+ }
+
+ void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot) {
+ final int taskId = startingWindowInfo.taskInfo.taskId;
+ // Remove any existing starting window for this task before adding.
+ mStartingWindowRecordManager.removeWindow(taskId, true);
+ final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo,
+ startingWindowInfo.appToken, snapshot, mMainExecutor,
+ () -> mStartingWindowRecordManager.removeWindow(taskId, true));
+ if (surface != null) {
+ final SnapshotWindowRecord tView = new SnapshotWindowRecord(surface,
+ startingWindowInfo.taskInfo.topActivityType, mMainExecutor);
+ mStartingWindowRecordManager.addRecord(taskId, tView);
+ }
+ }
+
+ private static class SnapshotWindowRecord extends StartingSurfaceDrawer.SnapshotRecord {
+ private final TaskSnapshotWindow mTaskSnapshotWindow;
+
+ SnapshotWindowRecord(TaskSnapshotWindow taskSnapshotWindow,
+ int activityType, ShellExecutor removeExecutor) {
+ super(activityType, removeExecutor);
+ mTaskSnapshotWindow = taskSnapshotWindow;
+ mBGColor = mTaskSnapshotWindow.getBackgroundColor();
+ }
+
+ @Override
+ protected void removeImmediately() {
+ super.removeImmediately();
+ mTaskSnapshotWindow.removeImmediately();
+ }
+
+ @Override
+ protected boolean hasImeSurface() {
+ return mTaskSnapshotWindow.hasImeSurface();
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index ebb957b..dc91a11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -24,9 +24,6 @@
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
-import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MAX_ANIMATION_DURATION;
-import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MINIMAL_ANIMATION_DURATION;
-
import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -96,6 +93,25 @@
public class SplashscreenContentDrawer {
private static final String TAG = StartingWindowController.TAG;
+ /**
+ * The minimum duration during which the splash screen is shown when the splash screen icon is
+ * animated.
+ */
+ static final long MINIMAL_ANIMATION_DURATION = 400L;
+
+ /**
+ * Allow the icon style splash screen to be displayed for longer to give time for the animation
+ * to finish, i.e. the extra buffer time to keep the splash screen if the animation is slightly
+ * longer than the {@link #MINIMAL_ANIMATION_DURATION} duration.
+ */
+ static final long TIME_WINDOW_DURATION = 100L;
+
+ /**
+ * The maximum duration during which the splash screen will be shown if the application is ready
+ * to show before the icon animation finishes.
+ */
+ static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION;
+
// The acceptable area ratio of foreground_icon_area/background_icon_area, if there is an
// icon which it's non-transparent foreground area is similar to it's background area, then
// do not enlarge the foreground drawable.
@@ -368,10 +384,10 @@
private static int estimateWindowBGColor(Drawable themeBGDrawable) {
final DrawableColorTester themeBGTester = new DrawableColorTester(
- themeBGDrawable, DrawableColorTester.TRANSPARENT_FILTER /* filterType */);
- if (themeBGTester.passFilterRatio() == 0) {
- // the window background is transparent, unable to draw
- Slog.w(TAG, "Window background is transparent, fill background with black color");
+ themeBGDrawable, DrawableColorTester.TRANSLUCENT_FILTER /* filterType */);
+ if (themeBGTester.passFilterRatio() != 1) {
+ // the window background is translucent, unable to draw
+ Slog.w(TAG, "Window background is translucent, fill background with black color");
return getSystemBGColor();
} else {
return themeBGTester.getDominateColor();
@@ -854,7 +870,7 @@
@Override
public float passFilterRatio() {
final int alpha = mColorDrawable.getAlpha();
- return (float) (alpha / 255);
+ return alpha / 255.0f;
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
new file mode 100644
index 0000000..8a4d4c2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2023 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.startingsurface;
+
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.ActivityThread;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.os.IBinder;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.widget.FrameLayout;
+import android.window.SplashScreenView;
+import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
+
+import com.android.internal.R;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ContrastColorUtil;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.function.Supplier;
+
+/**
+ * A class which able to draw splash screen as the starting window for a task.
+ *
+ * In order to speed up, there will use two threads to creating a splash screen in parallel.
+ * Right now we are still using PhoneWindow to create splash screen window, so the view is added to
+ * the ViewRootImpl, and those view won't be draw immediately because the ViewRootImpl will call
+ * scheduleTraversal to register a callback from Choreographer, so the drawing result of the view
+ * can synchronize on each frame.
+ *
+ * The bad thing is that we cannot decide when would Choreographer#doFrame happen, and drawing
+ * the AdaptiveIconDrawable object can be time consuming, so we use the splash-screen background
+ * thread to draw the AdaptiveIconDrawable object to a Bitmap and cache it to a BitmapShader after
+ * the SplashScreenView just created, once we get the BitmapShader then the #draw call can be very
+ * quickly.
+ *
+ * So basically we are using the spare time to prepare the SplashScreenView while splash screen
+ * thread is waiting for
+ * 1. WindowManager#addView(binder call to WM),
+ * 2. Choreographer#doFrame happen(uncertain time for next frame, depends on device),
+ * 3. Session#relayout(another binder call to WM which under Choreographer#doFrame, but will
+ * always happen before #draw).
+ * Because above steps are running on splash-screen thread, so pre-draw the BitmapShader on
+ * splash-screen background tread can make they execute in parallel, which ensure it is faster then
+ * to draw the AdaptiveIconDrawable when receive callback from Choreographer#doFrame.
+ *
+ * Here is the sequence to compare the difference between using single and two thread.
+ *
+ * Single thread:
+ * => makeSplashScreenContentView -> WM#addView .. waiting for Choreographer#doFrame -> relayout
+ * -> draw -> AdaptiveIconDrawable#draw
+ *
+ * Two threads:
+ * => makeSplashScreenContentView -> cachePaint(=AdaptiveIconDrawable#draw)
+ * => WM#addView -> .. waiting for Choreographer#doFrame -> relayout -> draw -> (draw the Paint
+ * directly).
+ */
+class SplashscreenWindowCreator extends AbsSplashWindowCreator {
+ private static final int LIGHT_BARS_MASK =
+ WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
+ | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+
+ private final WindowManagerGlobal mWindowManagerGlobal;
+ private Choreographer mChoreographer;
+
+ /**
+ * Records of {@link SurfaceControlViewHost} where the splash screen icon animation is
+ * rendered and that have not yet been removed by their client.
+ */
+ private final SparseArray<SurfaceControlViewHost> mAnimatedSplashScreenSurfaceHosts =
+ new SparseArray<>(1);
+
+ SplashscreenWindowCreator(SplashscreenContentDrawer contentDrawer, Context context,
+ ShellExecutor splashScreenExecutor, DisplayManager displayManager,
+ StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager) {
+ super(contentDrawer, context, splashScreenExecutor, displayManager,
+ startingWindowRecordManager);
+ mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
+ mWindowManagerGlobal = WindowManagerGlobal.getInstance();
+ }
+
+ void addSplashScreenStartingWindow(StartingWindowInfo windowInfo,
+ @StartingWindowInfo.StartingWindowType int suggestType) {
+ final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
+ final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
+ ? windowInfo.targetActivityInfo
+ : taskInfo.topActivityInfo;
+ if (activityInfo == null || activityInfo.packageName == null) {
+ return;
+ }
+ // replace with the default theme if the application didn't set
+ final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
+ final Context context = SplashscreenContentDrawer.createContext(mContext, windowInfo, theme,
+ suggestType, mDisplayManager);
+ if (context == null) {
+ return;
+ }
+ final WindowManager.LayoutParams params = SplashscreenContentDrawer.createLayoutParameters(
+ context, windowInfo, suggestType, activityInfo.packageName,
+ suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
+ ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT, windowInfo.appToken);
+
+ final int displayId = taskInfo.displayId;
+ final int taskId = taskInfo.taskId;
+ final Display display = getDisplay(displayId);
+
+ // TODO(b/173975965) tracking performance
+ // Prepare the splash screen content view on splash screen worker thread in parallel, so the
+ // content view won't be blocked by binder call like addWindow and relayout.
+ // 1. Trigger splash screen worker thread to create SplashScreenView before/while
+ // Session#addWindow.
+ // 2. Synchronize the SplashscreenView to splash screen thread before Choreographer start
+ // traversal, which will call Session#relayout on splash screen thread.
+ // 3. Pre-draw the BitmapShader if the icon is immobile on splash screen worker thread, at
+ // the same time the splash screen thread should be executing Session#relayout. Blocking the
+ // traversal -> draw on splash screen thread until the BitmapShader of the icon is ready.
+
+ // Record whether create splash screen view success, notify to current thread after
+ // create splash screen view finished.
+ final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
+ final FrameLayout rootLayout = new FrameLayout(
+ mSplashscreenContentDrawer.createViewContextWrapper(context));
+ rootLayout.setPadding(0, 0, 0, 0);
+ rootLayout.setFitsSystemWindows(false);
+ final Runnable setViewSynchronized = () -> {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView");
+ // waiting for setContentView before relayoutWindow
+ SplashScreenView contentView = viewSupplier.get();
+ final StartingSurfaceDrawer.StartingWindowRecord sRecord =
+ mStartingWindowRecordManager.getRecord(taskId);
+ final SplashWindowRecord record = sRecord instanceof SplashWindowRecord
+ ? (SplashWindowRecord) sRecord : null;
+ // If record == null, either the starting window added fail or removed already.
+ // Do not add this view if the token is mismatch.
+ if (record != null && windowInfo.appToken == record.mAppToken) {
+ // if view == null then creation of content view was failed.
+ if (contentView != null) {
+ try {
+ rootLayout.addView(contentView);
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "failed set content view to starting window "
+ + "at taskId: " + taskId, e);
+ contentView = null;
+ }
+ }
+ record.setSplashScreenView(contentView);
+ }
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ };
+ requestTopUi(true);
+ mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
+ viewSupplier::setView, viewSupplier::setUiThreadInitTask);
+ try {
+ if (addWindow(taskId, windowInfo.appToken, rootLayout, display, params, suggestType)) {
+ // We use the splash screen worker thread to create SplashScreenView while adding
+ // the window, as otherwise Choreographer#doFrame might be delayed on this thread.
+ // And since Choreographer#doFrame won't happen immediately after adding the window,
+ // if the view is not added to the PhoneWindow on the first #doFrame, the view will
+ // not be rendered on the first frame. So here we need to synchronize the view on
+ // the window before first round relayoutWindow, which will happen after insets
+ // animation.
+ mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null);
+ final SplashWindowRecord record =
+ (SplashWindowRecord) mStartingWindowRecordManager.getRecord(taskId);
+ if (record != null) {
+ record.parseAppSystemBarColor(context);
+ // Block until we get the background color.
+ final SplashScreenView contentView = viewSupplier.get();
+ if (suggestType != STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
+ contentView.addOnAttachStateChangeListener(
+ new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ final int lightBarAppearance =
+ ContrastColorUtil.isColorLight(
+ contentView.getInitBackgroundColor())
+ ? LIGHT_BARS_MASK : 0;
+ contentView.getWindowInsetsController()
+ .setSystemBarsAppearance(
+ lightBarAppearance, LIGHT_BARS_MASK);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+ });
+ }
+ }
+ } else {
+ // release the icon view host
+ final SplashScreenView contentView = viewSupplier.get();
+ if (contentView.getSurfaceHost() != null) {
+ SplashScreenView.releaseIconHost(contentView.getSurfaceHost());
+ }
+ }
+ } catch (RuntimeException e) {
+ // don't crash if something else bad happens, for example a
+ // failure loading resources because we are loading from an app
+ // on external storage that has been unmounted.
+ Slog.w(TAG, "failed creating starting window at taskId: " + taskId, e);
+ }
+ }
+
+ int estimateTaskBackgroundColor(TaskInfo taskInfo) {
+ if (taskInfo.topActivityInfo == null) {
+ return Color.TRANSPARENT;
+ }
+ final ActivityInfo activityInfo = taskInfo.topActivityInfo;
+ final String packageName = activityInfo.packageName;
+ final int userId = taskInfo.userId;
+ final Context windowContext;
+ try {
+ windowContext = mContext.createPackageContextAsUser(
+ packageName, Context.CONTEXT_RESTRICTED, UserHandle.of(userId));
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Failed creating package context with package name "
+ + packageName + " for user " + taskInfo.userId, e);
+ return Color.TRANSPARENT;
+ }
+ try {
+ final IPackageManager packageManager = ActivityThread.getPackageManager();
+ final String splashScreenThemeName = packageManager.getSplashScreenTheme(packageName,
+ userId);
+ final int splashScreenThemeId = splashScreenThemeName != null
+ ? windowContext.getResources().getIdentifier(splashScreenThemeName, null, null)
+ : 0;
+
+ final int theme = getSplashScreenTheme(splashScreenThemeId, activityInfo);
+
+ if (theme != windowContext.getThemeResId()) {
+ windowContext.setTheme(theme);
+ }
+ return mSplashscreenContentDrawer.estimateTaskBackgroundColor(windowContext);
+ } catch (RuntimeException | RemoteException e) {
+ Slog.w(TAG, "failed get starting window background color at taskId: "
+ + taskInfo.taskId, e);
+ }
+ return Color.TRANSPARENT;
+ }
+
+ /**
+ * Called when the Task wants to copy the splash screen.
+ */
+ public void copySplashScreenView(int taskId) {
+ final StartingSurfaceDrawer.StartingWindowRecord record =
+ mStartingWindowRecordManager.getRecord(taskId);
+ final SplashWindowRecord preView = record instanceof SplashWindowRecord
+ ? (SplashWindowRecord) record : null;
+ SplashScreenView.SplashScreenViewParcelable parcelable;
+ SplashScreenView splashScreenView = preView != null ? preView.mSplashView : null;
+ if (splashScreenView != null && splashScreenView.isCopyable()) {
+ parcelable = new SplashScreenView.SplashScreenViewParcelable(splashScreenView);
+ parcelable.setClientCallback(
+ new RemoteCallback((bundle) -> mSplashScreenExecutor.execute(
+ () -> onAppSplashScreenViewRemoved(taskId, false))));
+ splashScreenView.onCopied();
+ mAnimatedSplashScreenSurfaceHosts.append(taskId, splashScreenView.getSurfaceHost());
+ } else {
+ parcelable = null;
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Copying splash screen window view for task: %d with parcelable %b",
+ taskId, parcelable != null);
+ ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
+ }
+
+ /**
+ * Called when the {@link SplashScreenView} is removed from the client Activity view's hierarchy
+ * or when the Activity is clean up.
+ *
+ * @param taskId The Task id on which the splash screen was attached
+ */
+ public void onAppSplashScreenViewRemoved(int taskId) {
+ onAppSplashScreenViewRemoved(taskId, true /* fromServer */);
+ }
+
+ /**
+ * @param fromServer If true, this means the removal was notified by the server. This is only
+ * used for debugging purposes.
+ * @see #onAppSplashScreenViewRemoved(int)
+ */
+ private void onAppSplashScreenViewRemoved(int taskId, boolean fromServer) {
+ SurfaceControlViewHost viewHost =
+ mAnimatedSplashScreenSurfaceHosts.get(taskId);
+ if (viewHost == null) {
+ return;
+ }
+ mAnimatedSplashScreenSurfaceHosts.remove(taskId);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "%s the splash screen. Releasing SurfaceControlViewHost for task: %d",
+ fromServer ? "Server cleaned up" : "App removed", taskId);
+ SplashScreenView.releaseIconHost(viewHost);
+ }
+
+ protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
+ WindowManager.LayoutParams params,
+ @StartingWindowInfo.StartingWindowType int suggestType) {
+ boolean shouldSaveView = true;
+ final Context context = view.getContext();
+ try {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView");
+ mWindowManagerGlobal.addView(view, params, display,
+ null /* parentWindow */, context.getUserId());
+ } catch (WindowManager.BadTokenException e) {
+ // ignore
+ Slog.w(TAG, appToken + " already running, starting window not displayed. "
+ + e.getMessage());
+ shouldSaveView = false;
+ } finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ if (view.getParent() == null) {
+ Slog.w(TAG, "view not successfully added to wm, removing view");
+ mWindowManagerGlobal.removeView(view, true /* immediate */);
+ shouldSaveView = false;
+ }
+ }
+ if (shouldSaveView) {
+ mStartingWindowRecordManager.removeWindow(taskId, true);
+ saveSplashScreenRecord(appToken, taskId, view, suggestType);
+ }
+ return shouldSaveView;
+ }
+
+ private void saveSplashScreenRecord(IBinder appToken, int taskId, View view,
+ @StartingWindowInfo.StartingWindowType int suggestType) {
+ final SplashWindowRecord tView =
+ new SplashWindowRecord(appToken, view, suggestType);
+ mStartingWindowRecordManager.addRecord(taskId, tView);
+ }
+
+ private void removeWindowInner(View decorView, boolean hideView) {
+ requestTopUi(false);
+ if (hideView) {
+ decorView.setVisibility(View.GONE);
+ }
+ mWindowManagerGlobal.removeView(decorView, false /* immediate */);
+ }
+
+ private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> {
+ private SplashScreenView mView;
+ private boolean mIsViewSet;
+ private Runnable mUiThreadInitTask;
+ void setView(SplashScreenView view) {
+ synchronized (this) {
+ mView = view;
+ mIsViewSet = true;
+ notify();
+ }
+ }
+
+ void setUiThreadInitTask(Runnable initTask) {
+ synchronized (this) {
+ mUiThreadInitTask = initTask;
+ }
+ }
+
+ @Override
+ @Nullable
+ public SplashScreenView get() {
+ synchronized (this) {
+ while (!mIsViewSet) {
+ try {
+ wait();
+ } catch (InterruptedException ignored) {
+ }
+ }
+ if (mUiThreadInitTask != null) {
+ mUiThreadInitTask.run();
+ mUiThreadInitTask = null;
+ }
+ return mView;
+ }
+ }
+ }
+
+ private class SplashWindowRecord extends StartingSurfaceDrawer.StartingWindowRecord {
+ private final IBinder mAppToken;
+ private final View mRootView;
+ @StartingWindowInfo.StartingWindowType private final int mSuggestType;
+ private final long mCreateTime;
+
+ private boolean mSetSplashScreen;
+ private SplashScreenView mSplashView;
+ private int mSystemBarAppearance;
+ private boolean mDrawsSystemBarBackgrounds;
+
+ SplashWindowRecord(IBinder appToken, View decorView,
+ @StartingWindowInfo.StartingWindowType int suggestType) {
+ mAppToken = appToken;
+ mRootView = decorView;
+ mSuggestType = suggestType;
+ mCreateTime = SystemClock.uptimeMillis();
+ }
+
+ void setSplashScreenView(@Nullable SplashScreenView splashScreenView) {
+ if (mSetSplashScreen) {
+ return;
+ }
+ mSplashView = splashScreenView;
+ mBGColor = mSplashView != null ? mSplashView.getInitBackgroundColor()
+ : Color.TRANSPARENT;
+ mSetSplashScreen = true;
+ }
+
+ void parseAppSystemBarColor(Context context) {
+ final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
+ mDrawsSystemBarBackgrounds = a.getBoolean(
+ R.styleable.Window_windowDrawsSystemBarBackgrounds, false);
+ if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
+ mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+ }
+ if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
+ mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+ }
+ a.recycle();
+ }
+
+ // Reset the system bar color which set by splash screen, make it align to the app.
+ void clearSystemBarColor() {
+ if (mRootView == null || !mRootView.isAttachedToWindow()) {
+ return;
+ }
+ if (mRootView.getLayoutParams() instanceof WindowManager.LayoutParams) {
+ final WindowManager.LayoutParams lp =
+ (WindowManager.LayoutParams) mRootView.getLayoutParams();
+ if (mDrawsSystemBarBackgrounds) {
+ lp.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ } else {
+ lp.flags &= ~WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ }
+ mRootView.setLayoutParams(lp);
+ }
+ mRootView.getWindowInsetsController().setSystemBarsAppearance(
+ mSystemBarAppearance, LIGHT_BARS_MASK);
+ }
+
+ @Override
+ public void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
+ if (mRootView == null) {
+ return;
+ }
+ if (mSplashView == null) {
+ // shouldn't happen, the app window may be drawn earlier than starting window?
+ Slog.e(TAG, "Found empty splash screen, remove!");
+ removeWindowInner(mRootView, false);
+ return;
+ }
+ clearSystemBarColor();
+ if (immediately
+ || mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
+ removeWindowInner(mRootView, false);
+ } else {
+ if (info.playRevealAnimation) {
+ mSplashscreenContentDrawer.applyExitAnimation(mSplashView,
+ info.windowAnimationLeash, info.mainFrame,
+ () -> removeWindowInner(mRootView, true),
+ mCreateTime, info.roundedCornerRadius);
+ } else {
+ // the SplashScreenView has been copied to client, hide the view to skip
+ // default exit animation
+ removeWindowInner(mRootView, true);
+ }
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 4f07bfe..ff06db3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -16,169 +16,80 @@
package com.android.wm.shell.startingsurface;
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
-import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
-import android.annotation.Nullable;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityTaskManager;
-import android.app.ActivityThread;
+import android.annotation.CallSuper;
import android.app.TaskInfo;
+import android.app.WindowConfiguration;
import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.content.res.TypedArray;
+import android.content.res.Configuration;
import android.graphics.Color;
-import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
-import android.os.IBinder;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.util.Slog;
import android.util.SparseArray;
-import android.view.Choreographer;
-import android.view.Display;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.WindowInsetsController;
+import android.view.IWindow;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
-import android.widget.FrameLayout;
+import android.view.WindowlessWindowManager;
import android.window.SplashScreenView;
-import android.window.SplashScreenView.SplashScreenViewParcelable;
import android.window.StartingWindowInfo;
import android.window.StartingWindowInfo.StartingWindowType;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskSnapshot;
-import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.ContrastColorUtil;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import java.util.function.Supplier;
-
/**
* A class which able to draw splash screen or snapshot as the starting window for a task.
- *
- * In order to speed up, there will use two threads to creating a splash screen in parallel.
- * Right now we are still using PhoneWindow to create splash screen window, so the view is added to
- * the ViewRootImpl, and those view won't be draw immediately because the ViewRootImpl will call
- * scheduleTraversal to register a callback from Choreographer, so the drawing result of the view
- * can synchronize on each frame.
- *
- * The bad thing is that we cannot decide when would Choreographer#doFrame happen, and drawing
- * the AdaptiveIconDrawable object can be time consuming, so we use the splash-screen background
- * thread to draw the AdaptiveIconDrawable object to a Bitmap and cache it to a BitmapShader after
- * the SplashScreenView just created, once we get the BitmapShader then the #draw call can be very
- * quickly.
- *
- * So basically we are using the spare time to prepare the SplashScreenView while splash screen
- * thread is waiting for
- * 1. WindowManager#addView(binder call to WM),
- * 2. Choreographer#doFrame happen(uncertain time for next frame, depends on device),
- * 3. Session#relayout(another binder call to WM which under Choreographer#doFrame, but will
- * always happen before #draw).
- * Because above steps are running on splash-screen thread, so pre-draw the BitmapShader on
- * splash-screen background tread can make they execute in parallel, which ensure it is faster then
- * to draw the AdaptiveIconDrawable when receive callback from Choreographer#doFrame.
- *
- * Here is the sequence to compare the difference between using single and two thread.
- *
- * Single thread:
- * => makeSplashScreenContentView -> WM#addView .. waiting for Choreographer#doFrame -> relayout
- * -> draw -> AdaptiveIconDrawable#draw
- *
- * Two threads:
- * => makeSplashScreenContentView -> cachePaint(=AdaptiveIconDrawable#draw)
- * => WM#addView -> .. waiting for Choreographer#doFrame -> relayout -> draw -> (draw the Paint
- * directly).
*/
@ShellSplashscreenThread
public class StartingSurfaceDrawer {
- private static final String TAG = StartingWindowController.TAG;
- private final Context mContext;
- private final DisplayManager mDisplayManager;
private final ShellExecutor mSplashScreenExecutor;
@VisibleForTesting
final SplashscreenContentDrawer mSplashscreenContentDrawer;
- private Choreographer mChoreographer;
- private final WindowManagerGlobal mWindowManagerGlobal;
- private StartingSurface.SysuiProxy mSysuiProxy;
- private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo();
+ @VisibleForTesting
+ final SplashscreenWindowCreator mSplashscreenWindowCreator;
+ private final SnapshotWindowCreator mSnapshotWindowCreator;
+ private final WindowlessSplashWindowCreator mWindowlessSplashWindowCreator;
+ private final WindowlessSnapshotWindowCreator mWindowlessSnapshotWindowCreator;
- private static final int LIGHT_BARS_MASK =
- WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
- | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
- /**
- * The minimum duration during which the splash screen is shown when the splash screen icon is
- * animated.
- */
- static final long MINIMAL_ANIMATION_DURATION = 400L;
-
- /**
- * Allow the icon style splash screen to be displayed for longer to give time for the animation
- * to finish, i.e. the extra buffer time to keep the splash screen if the animation is slightly
- * longer than the {@link #MINIMAL_ANIMATION_DURATION} duration.
- */
- static final long TIME_WINDOW_DURATION = 100L;
-
- /**
- * The maximum duration during which the splash screen will be shown if the application is ready
- * to show before the icon animation finishes.
- */
- static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION;
-
+ @VisibleForTesting
+ final StartingWindowRecordManager mWindowRecords = new StartingWindowRecordManager();
+ // Windowless surface could co-exist with starting window in a task.
+ @VisibleForTesting
+ final StartingWindowRecordManager mWindowlessRecords = new StartingWindowRecordManager();
/**
* @param splashScreenExecutor The thread used to control add and remove starting window.
*/
public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
IconProvider iconProvider, TransactionPool pool) {
- mContext = context;
- mDisplayManager = mContext.getSystemService(DisplayManager.class);
mSplashScreenExecutor = splashScreenExecutor;
- mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool);
- mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
- mWindowManagerGlobal = WindowManagerGlobal.getInstance();
- mDisplayManager.getDisplay(DEFAULT_DISPLAY);
- }
+ final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+ mSplashscreenContentDrawer = new SplashscreenContentDrawer(context, iconProvider, pool);
+ displayManager.getDisplay(DEFAULT_DISPLAY);
- @VisibleForTesting
- final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>();
-
- /**
- * Records of {@link SurfaceControlViewHost} where the splash screen icon animation is
- * rendered and that have not yet been removed by their client.
- */
- private final SparseArray<SurfaceControlViewHost> mAnimatedSplashScreenSurfaceHosts =
- new SparseArray<>(1);
-
- private Display getDisplay(int displayId) {
- return mDisplayManager.getDisplay(displayId);
- }
-
- int getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo) {
- return splashScreenThemeResId != 0
- ? splashScreenThemeResId
- : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource()
- : com.android.internal.R.style.Theme_DeviceDefault_DayNight;
+ mSplashscreenWindowCreator = new SplashscreenWindowCreator(mSplashscreenContentDrawer,
+ context, splashScreenExecutor, displayManager, mWindowRecords);
+ mSnapshotWindowCreator = new SnapshotWindowCreator(splashScreenExecutor,
+ mWindowRecords);
+ mWindowlessSplashWindowCreator = new WindowlessSplashWindowCreator(
+ mSplashscreenContentDrawer, context, splashScreenExecutor, displayManager,
+ mWindowlessRecords, pool);
+ mWindowlessSnapshotWindowCreator = new WindowlessSnapshotWindowCreator(
+ mWindowlessRecords, context, displayManager, mSplashscreenContentDrawer, pool);
}
void setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy) {
- mSysuiProxy = sysuiProxy;
+ mSplashscreenWindowCreator.setSysuiProxy(sysuiProxy);
+ mWindowlessSplashWindowCreator.setSysuiProxy(sysuiProxy);
}
/**
@@ -186,231 +97,55 @@
*
* @param suggestType The suggestion type to draw the splash screen.
*/
- void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken,
+ void addSplashScreenStartingWindow(StartingWindowInfo windowInfo,
@StartingWindowType int suggestType) {
- final RunningTaskInfo taskInfo = windowInfo.taskInfo;
- final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
- ? windowInfo.targetActivityInfo
- : taskInfo.topActivityInfo;
- if (activityInfo == null || activityInfo.packageName == null) {
- return;
- }
- // replace with the default theme if the application didn't set
- final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
- final Context context = SplashscreenContentDrawer.createContext(mContext, windowInfo, theme,
- suggestType, mDisplayManager);
- if (context == null) {
- return;
- }
- final WindowManager.LayoutParams params = SplashscreenContentDrawer.createLayoutParameters(
- context, windowInfo, suggestType, activityInfo.packageName,
- suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
- ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT, appToken);
-
- final int displayId = taskInfo.displayId;
- final int taskId = taskInfo.taskId;
- final Display display = getDisplay(displayId);
-
- // TODO(b/173975965) tracking performance
- // Prepare the splash screen content view on splash screen worker thread in parallel, so the
- // content view won't be blocked by binder call like addWindow and relayout.
- // 1. Trigger splash screen worker thread to create SplashScreenView before/while
- // Session#addWindow.
- // 2. Synchronize the SplashscreenView to splash screen thread before Choreographer start
- // traversal, which will call Session#relayout on splash screen thread.
- // 3. Pre-draw the BitmapShader if the icon is immobile on splash screen worker thread, at
- // the same time the splash screen thread should be executing Session#relayout. Blocking the
- // traversal -> draw on splash screen thread until the BitmapShader of the icon is ready.
-
- // Record whether create splash screen view success, notify to current thread after
- // create splash screen view finished.
- final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
- final FrameLayout rootLayout = new FrameLayout(
- mSplashscreenContentDrawer.createViewContextWrapper(context));
- rootLayout.setPadding(0, 0, 0, 0);
- rootLayout.setFitsSystemWindows(false);
- final Runnable setViewSynchronized = () -> {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView");
- // waiting for setContentView before relayoutWindow
- SplashScreenView contentView = viewSupplier.get();
- final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
- // If record == null, either the starting window added fail or removed already.
- // Do not add this view if the token is mismatch.
- if (record != null && appToken == record.mAppToken) {
- // if view == null then creation of content view was failed.
- if (contentView != null) {
- try {
- rootLayout.addView(contentView);
- } catch (RuntimeException e) {
- Slog.w(TAG, "failed set content view to starting window "
- + "at taskId: " + taskId, e);
- contentView = null;
- }
- }
- record.setSplashScreenView(contentView);
- }
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- };
- if (mSysuiProxy != null) {
- mSysuiProxy.requestTopUi(true, TAG);
- }
- mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
- viewSupplier::setView, viewSupplier::setUiThreadInitTask);
- try {
- if (addWindow(taskId, appToken, rootLayout, display, params, suggestType)) {
- // We use the splash screen worker thread to create SplashScreenView while adding
- // the window, as otherwise Choreographer#doFrame might be delayed on this thread.
- // And since Choreographer#doFrame won't happen immediately after adding the window,
- // if the view is not added to the PhoneWindow on the first #doFrame, the view will
- // not be rendered on the first frame. So here we need to synchronize the view on
- // the window before first round relayoutWindow, which will happen after insets
- // animation.
- mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null);
- final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
- record.parseAppSystemBarColor(context);
- // Block until we get the background color.
- final SplashScreenView contentView = viewSupplier.get();
- if (suggestType != STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
- contentView.addOnAttachStateChangeListener(
- new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- final int lightBarAppearance = ContrastColorUtil.isColorLight(
- contentView.getInitBackgroundColor())
- ? LIGHT_BARS_MASK : 0;
- contentView.getWindowInsetsController().setSystemBarsAppearance(
- lightBarAppearance, LIGHT_BARS_MASK);
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- }
- });
- }
- record.mBGColor = contentView.getInitBackgroundColor();
- } else {
- // release the icon view host
- final SplashScreenView contentView = viewSupplier.get();
- if (contentView.getSurfaceHost() != null) {
- SplashScreenView.releaseIconHost(contentView.getSurfaceHost());
- }
- }
- } catch (RuntimeException e) {
- // don't crash if something else bad happens, for example a
- // failure loading resources because we are loading from an app
- // on external storage that has been unmounted.
- Slog.w(TAG, "failed creating starting window at taskId: " + taskId, e);
- }
+ mSplashscreenWindowCreator.addSplashScreenStartingWindow(windowInfo, suggestType);
}
int getStartingWindowBackgroundColorForTask(int taskId) {
- final StartingWindowRecord startingWindowRecord = mStartingWindowRecords.get(taskId);
+ final StartingWindowRecord startingWindowRecord = mWindowRecords.getRecord(taskId);
if (startingWindowRecord == null) {
return Color.TRANSPARENT;
}
- return startingWindowRecord.mBGColor;
- }
-
- private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> {
- private SplashScreenView mView;
- private boolean mIsViewSet;
- private Runnable mUiThreadInitTask;
- void setView(SplashScreenView view) {
- synchronized (this) {
- mView = view;
- mIsViewSet = true;
- notify();
- }
- }
-
- void setUiThreadInitTask(Runnable initTask) {
- synchronized (this) {
- mUiThreadInitTask = initTask;
- }
- }
-
- @Override
- @Nullable
- public SplashScreenView get() {
- synchronized (this) {
- while (!mIsViewSet) {
- try {
- wait();
- } catch (InterruptedException ignored) {
- }
- }
- if (mUiThreadInitTask != null) {
- mUiThreadInitTask.run();
- mUiThreadInitTask = null;
- }
- return mView;
- }
- }
+ return startingWindowRecord.getBGColor();
}
int estimateTaskBackgroundColor(TaskInfo taskInfo) {
- if (taskInfo.topActivityInfo == null) {
- return Color.TRANSPARENT;
- }
- final ActivityInfo activityInfo = taskInfo.topActivityInfo;
- final String packageName = activityInfo.packageName;
- final int userId = taskInfo.userId;
- final Context windowContext;
- try {
- windowContext = mContext.createPackageContextAsUser(
- packageName, Context.CONTEXT_RESTRICTED, UserHandle.of(userId));
- } catch (PackageManager.NameNotFoundException e) {
- Slog.w(TAG, "Failed creating package context with package name "
- + packageName + " for user " + taskInfo.userId, e);
- return Color.TRANSPARENT;
- }
- try {
- final IPackageManager packageManager = ActivityThread.getPackageManager();
- final String splashScreenThemeName = packageManager.getSplashScreenTheme(packageName,
- userId);
- final int splashScreenThemeId = splashScreenThemeName != null
- ? windowContext.getResources().getIdentifier(splashScreenThemeName, null, null)
- : 0;
-
- final int theme = getSplashScreenTheme(splashScreenThemeId, activityInfo);
-
- if (theme != windowContext.getThemeResId()) {
- windowContext.setTheme(theme);
- }
- return mSplashscreenContentDrawer.estimateTaskBackgroundColor(windowContext);
- } catch (RuntimeException | RemoteException e) {
- Slog.w(TAG, "failed get starting window background color at taskId: "
- + taskInfo.taskId, e);
- }
- return Color.TRANSPARENT;
+ return mSplashscreenWindowCreator.estimateTaskBackgroundColor(taskInfo);
}
/**
* Called when a task need a snapshot starting window.
*/
- void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, IBinder appToken,
- TaskSnapshot snapshot) {
- final int taskId = startingWindowInfo.taskInfo.taskId;
- // Remove any existing starting window for this task before adding.
- removeWindowNoAnimate(taskId);
- final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, appToken,
- snapshot, mSplashScreenExecutor, () -> removeWindowNoAnimate(taskId));
- if (surface == null) {
- return;
- }
- final StartingWindowRecord tView = new StartingWindowRecord(appToken,
- null/* decorView */, surface, STARTING_WINDOW_TYPE_SNAPSHOT);
- mStartingWindowRecords.put(taskId, tView);
+ void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot) {
+ mSnapshotWindowCreator.makeTaskSnapshotWindow(startingWindowInfo, snapshot);
}
/**
* Called when the content of a task is ready to show, starting window can be removed.
*/
public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Task start finish, remove starting surface for task: %d",
- removalInfo.taskId);
- removeWindowSynced(removalInfo, false /* immediately */);
+ if (removalInfo.windowlessSurface) {
+ mWindowlessRecords.removeWindow(removalInfo, removalInfo.removeImmediately);
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Task start finish, remove starting surface for task: %d",
+ removalInfo.taskId);
+ mWindowRecords.removeWindow(removalInfo, removalInfo.removeImmediately);
+ }
+ }
+
+ /**
+ * Create a windowless starting surface and attach to the root surface.
+ */
+ void addWindowlessStartingSurface(StartingWindowInfo windowInfo) {
+ if (windowInfo.taskSnapshot != null) {
+ mWindowlessSnapshotWindowCreator.makeTaskSnapshotWindow(windowInfo,
+ windowInfo.rootSurface, windowInfo.taskSnapshot, mSplashScreenExecutor);
+ } else {
+ mWindowlessSplashWindowCreator.addSplashScreenStartingWindow(
+ windowInfo, windowInfo.rootSurface);
+ }
}
/**
@@ -419,37 +154,15 @@
public void clearAllWindows() {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"Clear all starting windows immediately");
- final int taskSize = mStartingWindowRecords.size();
- final int[] taskIds = new int[taskSize];
- for (int i = taskSize - 1; i >= 0; --i) {
- taskIds[i] = mStartingWindowRecords.keyAt(i);
- }
- for (int i = taskSize - 1; i >= 0; --i) {
- removeWindowNoAnimate(taskIds[i]);
- }
+ mWindowRecords.clearAllWindows();
+ mWindowlessRecords.clearAllWindows();
}
/**
* Called when the Task wants to copy the splash screen.
*/
public void copySplashScreenView(int taskId) {
- final StartingWindowRecord preView = mStartingWindowRecords.get(taskId);
- SplashScreenViewParcelable parcelable;
- SplashScreenView splashScreenView = preView != null ? preView.mContentView : null;
- if (splashScreenView != null && splashScreenView.isCopyable()) {
- parcelable = new SplashScreenViewParcelable(splashScreenView);
- parcelable.setClientCallback(
- new RemoteCallback((bundle) -> mSplashScreenExecutor.execute(
- () -> onAppSplashScreenViewRemoved(taskId, false))));
- splashScreenView.onCopied();
- mAnimatedSplashScreenSurfaceHosts.append(taskId, splashScreenView.getSurfaceHost());
- } else {
- parcelable = null;
- }
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Copying splash screen window view for task: %d with parcelable %b",
- taskId, parcelable != null);
- ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
+ mSplashscreenWindowCreator.copySplashScreenView(taskId);
}
/**
@@ -459,195 +172,148 @@
* @param taskId The Task id on which the splash screen was attached
*/
public void onAppSplashScreenViewRemoved(int taskId) {
- onAppSplashScreenViewRemoved(taskId, true /* fromServer */);
- }
-
- /**
- * @param fromServer If true, this means the removal was notified by the server. This is only
- * used for debugging purposes.
- * @see #onAppSplashScreenViewRemoved(int)
- */
- private void onAppSplashScreenViewRemoved(int taskId, boolean fromServer) {
- SurfaceControlViewHost viewHost =
- mAnimatedSplashScreenSurfaceHosts.get(taskId);
- if (viewHost == null) {
- return;
- }
- mAnimatedSplashScreenSurfaceHosts.remove(taskId);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "%s the splash screen. Releasing SurfaceControlViewHost for task: %d",
- fromServer ? "Server cleaned up" : "App removed", taskId);
- SplashScreenView.releaseIconHost(viewHost);
- }
-
- protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
- WindowManager.LayoutParams params, @StartingWindowType int suggestType) {
- boolean shouldSaveView = true;
- final Context context = view.getContext();
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView");
- mWindowManagerGlobal.addView(view, params, display,
- null /* parentWindow */, context.getUserId());
- } catch (WindowManager.BadTokenException e) {
- // ignore
- Slog.w(TAG, appToken + " already running, starting window not displayed. "
- + e.getMessage());
- shouldSaveView = false;
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- if (view.getParent() == null) {
- Slog.w(TAG, "view not successfully added to wm, removing view");
- mWindowManagerGlobal.removeView(view, true /* immediate */);
- shouldSaveView = false;
- }
- }
- if (shouldSaveView) {
- removeWindowNoAnimate(taskId);
- saveSplashScreenRecord(appToken, taskId, view, suggestType);
- }
- return shouldSaveView;
- }
-
- @VisibleForTesting
- void saveSplashScreenRecord(IBinder appToken, int taskId, View view,
- @StartingWindowType int suggestType) {
- final StartingWindowRecord tView = new StartingWindowRecord(appToken, view,
- null/* TaskSnapshotWindow */, suggestType);
- mStartingWindowRecords.put(taskId, tView);
- }
-
- private void removeWindowNoAnimate(int taskId) {
- mTmpRemovalInfo.taskId = taskId;
- removeWindowSynced(mTmpRemovalInfo, true /* immediately */);
+ mSplashscreenWindowCreator.onAppSplashScreenViewRemoved(taskId);
}
void onImeDrawnOnTask(int taskId) {
- final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
- if (record != null && record.mTaskSnapshotWindow != null
- && record.mTaskSnapshotWindow.hasImeSurface()) {
- removeWindowNoAnimate(taskId);
+ onImeDrawnOnTask(mWindowRecords, taskId);
+ onImeDrawnOnTask(mWindowlessRecords, taskId);
+ }
+
+ private void onImeDrawnOnTask(StartingWindowRecordManager records, int taskId) {
+ final StartingSurfaceDrawer.StartingWindowRecord sRecord =
+ records.getRecord(taskId);
+ final SnapshotRecord record = sRecord instanceof SnapshotRecord
+ ? (SnapshotRecord) sRecord : null;
+ if (record != null && record.hasImeSurface()) {
+ records.removeWindow(taskId, true);
}
}
- protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo, boolean immediately) {
- final int taskId = removalInfo.taskId;
- final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
- if (record != null) {
- if (record.mDecorView != null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Removing splash screen window for task: %d", taskId);
- if (record.mContentView != null) {
- record.clearSystemBarColor();
- if (immediately
- || record.mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
- removeWindowInner(record.mDecorView, false);
- } else {
- if (removalInfo.playRevealAnimation) {
- mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
- removalInfo.windowAnimationLeash, removalInfo.mainFrame,
- () -> removeWindowInner(record.mDecorView, true),
- record.mCreateTime, removalInfo.roundedCornerRadius);
- } else {
- // the SplashScreenView has been copied to client, hide the view to skip
- // default exit animation
- removeWindowInner(record.mDecorView, true);
- }
- }
- } else {
- // shouldn't happen
- Slog.e(TAG, "Found empty splash screen, remove!");
- removeWindowInner(record.mDecorView, false);
- }
+ static class WindowlessStartingWindow extends WindowlessWindowManager {
+ SurfaceControl mChildSurface;
+ WindowlessStartingWindow(Configuration c, SurfaceControl rootSurface) {
+ super(c, rootSurface, null /* hostInputToken */);
+ }
+
+ @Override
+ protected SurfaceControl getParentSurface(IWindow window,
+ WindowManager.LayoutParams attrs) {
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+ .setContainerLayer()
+ .setName("Windowless window")
+ .setHidden(false)
+ .setParent(mRootSurface)
+ .setCallsite("WindowlessStartingWindow#attachToParentSurface");
+ mChildSurface = builder.build();
+ try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) {
+ t.setLayer(mChildSurface, Integer.MAX_VALUE);
+ t.apply();
}
- if (record.mTaskSnapshotWindow != null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Removing task snapshot window for %d", taskId);
- if (immediately) {
- record.mTaskSnapshotWindow.removeImmediately();
- } else {
- record.mTaskSnapshotWindow.scheduleRemove(removalInfo.deferRemoveForIme);
- }
- }
- mStartingWindowRecords.remove(taskId);
+ return mChildSurface;
+ }
+ }
+ abstract static class StartingWindowRecord {
+ protected int mBGColor;
+ abstract void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately);
+ int getBGColor() {
+ return mBGColor;
}
}
- private void removeWindowInner(View decorView, boolean hideView) {
- if (mSysuiProxy != null) {
- mSysuiProxy.requestTopUi(false, TAG);
- }
- if (hideView) {
- decorView.setVisibility(View.GONE);
- }
- mWindowManagerGlobal.removeView(decorView, false /* immediate */);
- }
+ abstract static class SnapshotRecord extends StartingWindowRecord {
+ private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
+ /**
+ * The max delay time in milliseconds for removing the task snapshot window with IME
+ * visible.
+ * Ideally the delay time will be shorter when receiving
+ * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
+ */
+ private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
+ private final Runnable mScheduledRunnable = this::removeImmediately;
- /**
- * Record the view or surface for a starting window.
- */
- private static class StartingWindowRecord {
- private final IBinder mAppToken;
- private final View mDecorView;
- private final TaskSnapshotWindow mTaskSnapshotWindow;
- private SplashScreenView mContentView;
- private boolean mSetSplashScreen;
- @StartingWindowType private int mSuggestType;
- private int mBGColor;
- private final long mCreateTime;
- private int mSystemBarAppearance;
- private boolean mDrawsSystemBarBackgrounds;
+ @WindowConfiguration.ActivityType protected final int mActivityType;
+ protected final ShellExecutor mRemoveExecutor;
- StartingWindowRecord(IBinder appToken, View decorView,
- TaskSnapshotWindow taskSnapshotWindow, @StartingWindowType int suggestType) {
- mAppToken = appToken;
- mDecorView = decorView;
- mTaskSnapshotWindow = taskSnapshotWindow;
- if (mTaskSnapshotWindow != null) {
- mBGColor = mTaskSnapshotWindow.getBackgroundColor();
+ SnapshotRecord(int activityType, ShellExecutor removeExecutor) {
+ mActivityType = activityType;
+ mRemoveExecutor = removeExecutor;
+ }
+
+ @Override
+ public final void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
+ if (immediately) {
+ removeImmediately();
+ } else {
+ scheduleRemove(info.deferRemoveForIme);
}
- mSuggestType = suggestType;
- mCreateTime = SystemClock.uptimeMillis();
}
- private void setSplashScreenView(SplashScreenView splashScreenView) {
- if (mSetSplashScreen) {
+ void scheduleRemove(boolean deferRemoveForIme) {
+ // Show the latest content as soon as possible for unlocking to home.
+ if (mActivityType == ACTIVITY_TYPE_HOME) {
+ removeImmediately();
return;
}
- mContentView = splashScreenView;
- mSetSplashScreen = true;
+ mRemoveExecutor.removeCallbacks(mScheduledRunnable);
+ final long delayRemovalTime = hasImeSurface() && deferRemoveForIme
+ ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
+ : DELAY_REMOVAL_TIME_GENERAL;
+ mRemoveExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Defer removing snapshot surface in %d", delayRemovalTime);
}
- private void parseAppSystemBarColor(Context context) {
- final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
- mDrawsSystemBarBackgrounds = a.getBoolean(
- R.styleable.Window_windowDrawsSystemBarBackgrounds, false);
- if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
- mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+ protected abstract boolean hasImeSurface();
+
+ @CallSuper
+ protected void removeImmediately() {
+ mRemoveExecutor.removeCallbacks(mScheduledRunnable);
+ }
+ }
+
+ static class StartingWindowRecordManager {
+ private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo();
+ private final SparseArray<StartingWindowRecord> mStartingWindowRecords =
+ new SparseArray<>();
+
+ void clearAllWindows() {
+ final int taskSize = mStartingWindowRecords.size();
+ final int[] taskIds = new int[taskSize];
+ for (int i = taskSize - 1; i >= 0; --i) {
+ taskIds[i] = mStartingWindowRecords.keyAt(i);
}
- if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
- mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+ for (int i = taskSize - 1; i >= 0; --i) {
+ removeWindow(taskIds[i], true);
}
- a.recycle();
}
- // Reset the system bar color which set by splash screen, make it align to the app.
- private void clearSystemBarColor() {
- if (mDecorView == null || !mDecorView.isAttachedToWindow()) {
- return;
+ void addRecord(int taskId, StartingWindowRecord record) {
+ mStartingWindowRecords.put(taskId, record);
+ }
+
+ void removeWindow(StartingWindowRemovalInfo removeInfo, boolean immediately) {
+ final int taskId = removeInfo.taskId;
+ final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
+ if (record != null) {
+ record.removeIfPossible(removeInfo, immediately);
+ mStartingWindowRecords.remove(taskId);
}
- if (mDecorView.getLayoutParams() instanceof WindowManager.LayoutParams) {
- final WindowManager.LayoutParams lp =
- (WindowManager.LayoutParams) mDecorView.getLayoutParams();
- if (mDrawsSystemBarBackgrounds) {
- lp.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- } else {
- lp.flags &= ~WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- }
- mDecorView.setLayoutParams(lp);
- }
- mDecorView.getWindowInsetsController().setSystemBarsAppearance(
- mSystemBarAppearance, LIGHT_BARS_MASK);
+ }
+
+ void removeWindow(int taskId, boolean immediately) {
+ mTmpRemovalInfo.taskId = taskId;
+ removeWindow(mTmpRemovalInfo, immediately);
+ }
+
+ StartingWindowRecord getRecord(int taskId) {
+ return mStartingWindowRecords.get(taskId);
+ }
+
+ @VisibleForTesting
+ int recordSize() {
+ return mStartingWindowRecords.size();
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index be2e793..bec4ba3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -21,6 +21,7 @@
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_WINDOWLESS;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
@@ -29,7 +30,6 @@
import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Color;
-import android.os.IBinder;
import android.os.Trace;
import android.util.SparseIntArray;
import android.window.StartingWindowInfo;
@@ -152,22 +152,23 @@
/**
* Called when a task need a starting window.
*/
- public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
+ public void addStartingWindow(StartingWindowInfo windowInfo) {
mSplashScreenExecutor.execute(() -> {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addStartingWindow");
final int suggestionType = mStartingWindowTypeAlgorithm.getSuggestedWindowType(
windowInfo);
final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
- if (isSplashScreenType(suggestionType)) {
- mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken,
- suggestionType);
+ if (suggestionType == STARTING_WINDOW_TYPE_WINDOWLESS) {
+ mStartingSurfaceDrawer.addWindowlessStartingSurface(windowInfo);
+ } else if (isSplashScreenType(suggestionType)) {
+ mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, suggestionType);
} else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
final TaskSnapshot snapshot = windowInfo.taskSnapshot;
- mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken,
- snapshot);
+ mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, snapshot);
}
- if (suggestionType != STARTING_WINDOW_TYPE_NONE) {
+ if (suggestionType != STARTING_WINDOW_TYPE_NONE
+ && suggestionType != STARTING_WINDOW_TYPE_WINDOWLESS) {
int taskId = runningTaskInfo.taskId;
int color = mStartingSurfaceDrawer
.getStartingWindowBackgroundColorForTask(taskId);
@@ -218,11 +219,13 @@
public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.removeStartingWindow(
removalInfo));
- mSplashScreenExecutor.executeDelayed(() -> {
- synchronized (mTaskBackgroundColors) {
- mTaskBackgroundColors.delete(removalInfo.taskId);
- }
- }, TASK_BG_COLOR_RETAIN_TIME_MS);
+ if (!removalInfo.windowlessSurface) {
+ mSplashScreenExecutor.executeDelayed(() -> {
+ synchronized (mTaskBackgroundColors) {
+ mTaskBackgroundColors.delete(removalInfo.taskId);
+ }
+ }, TASK_BG_COLOR_RETAIN_TIME_MS);
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index a05ed4f..c964df1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.startingsurface;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.graphics.Color.WHITE;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -63,24 +62,14 @@
private static final String TAG = StartingWindowController.TAG;
private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=";
- private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
- /**
- * The max delay time in milliseconds for removing the task snapshot window with IME visible.
- * Ideally the delay time will be shorter when receiving
- * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
- */
- private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
-
private final Window mWindow;
private final Runnable mClearWindowHandler;
private final ShellExecutor mSplashScreenExecutor;
private final IWindowSession mSession;
private boolean mHasDrawn;
private final Paint mBackgroundPaint = new Paint();
- private final int mActivityType;
private final int mOrientationOnCreation;
- private final Runnable mScheduledRunnable = this::removeImmediately;
private final boolean mHasImeSurface;
static TaskSnapshotWindow create(StartingWindowInfo info, IBinder appToken,
@@ -104,7 +93,6 @@
final Point taskSize = snapshot.getTaskSize();
final Rect taskBounds = new Rect(0, 0, taskSize.x, taskSize.y);
final int orientation = snapshot.getOrientation();
- final int activityType = runningTaskInfo.topActivityType;
final int displayId = runningTaskInfo.displayId;
final IWindowSession session = WindowManagerGlobal.getWindowSession();
@@ -114,16 +102,11 @@
final InsetsSourceControl.Array tmpControls = new InsetsSourceControl.Array();
final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
- final TaskDescription taskDescription;
- if (runningTaskInfo.taskDescription != null) {
- taskDescription = runningTaskInfo.taskDescription;
- } else {
- taskDescription = new TaskDescription();
- taskDescription.setBackgroundColor(WHITE);
- }
+ final TaskDescription taskDescription =
+ SnapshotDrawerUtils.getOrCreateTaskDescription(runningTaskInfo);
final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow(
- snapshot, taskDescription, orientation, activityType,
+ snapshot, taskDescription, orientation,
clearWindowHandler, splashScreenExecutor);
final Window window = snapshotSurface.mWindow;
@@ -153,6 +136,8 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} catch (RemoteException e) {
snapshotSurface.clearWindowSynced();
+ Slog.w(TAG, "Failed to relayout snapshot starting window");
+ return null;
}
SnapshotDrawerUtils.drawSnapshotOnSurface(info, layoutParams, surfaceControl, snapshot,
@@ -164,7 +149,7 @@
}
public TaskSnapshotWindow(TaskSnapshot snapshot, TaskDescription taskDescription,
- int currentOrientation, int activityType, Runnable clearWindowHandler,
+ int currentOrientation, Runnable clearWindowHandler,
ShellExecutor splashScreenExecutor) {
mSplashScreenExecutor = splashScreenExecutor;
mSession = WindowManagerGlobal.getWindowSession();
@@ -173,7 +158,6 @@
int backgroundColor = taskDescription.getBackgroundColor();
mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
mOrientationOnCreation = currentOrientation;
- mActivityType = activityType;
mClearWindowHandler = clearWindowHandler;
mHasImeSurface = snapshot.hasImeSurface();
}
@@ -186,23 +170,7 @@
return mHasImeSurface;
}
- void scheduleRemove(boolean deferRemoveForIme) {
- // Show the latest content as soon as possible for unlocking to home.
- if (mActivityType == ACTIVITY_TYPE_HOME) {
- removeImmediately();
- return;
- }
- mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
- final long delayRemovalTime = mHasImeSurface && deferRemoveForIme
- ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
- : DELAY_REMOVAL_TIME_GENERAL;
- mSplashScreenExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Defer removing snapshot surface in %d", delayRemovalTime);
- }
-
void removeImmediately() {
- mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
try {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"Removing taskSnapshot surface, mHasDrawn=%b", mHasDrawn);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
new file mode 100644
index 0000000..1445478
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2023 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.startingsurface;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.window.SnapshotDrawerUtils;
+import android.window.StartingWindowInfo;
+import android.window.TaskSnapshot;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+
+class WindowlessSnapshotWindowCreator {
+ private static final int DEFAULT_FADEOUT_DURATION = 233;
+ private final StartingSurfaceDrawer.StartingWindowRecordManager
+ mStartingWindowRecordManager;
+ private final DisplayManager mDisplayManager;
+ private final Context mContext;
+ private final SplashscreenContentDrawer mSplashscreenContentDrawer;
+ private final TransactionPool mTransactionPool;
+
+ WindowlessSnapshotWindowCreator(
+ StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager,
+ Context context,
+ DisplayManager displayManager, SplashscreenContentDrawer splashscreenContentDrawer,
+ TransactionPool transactionPool) {
+ mStartingWindowRecordManager = startingWindowRecordManager;
+ mContext = context;
+ mDisplayManager = displayManager;
+ mSplashscreenContentDrawer = splashscreenContentDrawer;
+ mTransactionPool = transactionPool;
+ }
+
+ void makeTaskSnapshotWindow(StartingWindowInfo info, SurfaceControl rootSurface,
+ TaskSnapshot snapshot, ShellExecutor removeExecutor) {
+ final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo;
+ final int taskId = runningTaskInfo.taskId;
+ final String title = "Windowless Snapshot " + taskId;
+ final WindowManager.LayoutParams lp = SnapshotDrawerUtils.createLayoutParameters(
+ info, title, TYPE_APPLICATION_OVERLAY, snapshot.getHardwareBuffer().getFormat(),
+ null /* token */);
+ if (lp == null) {
+ return;
+ }
+ final Display display = mDisplayManager.getDisplay(runningTaskInfo.displayId);
+ final StartingSurfaceDrawer.WindowlessStartingWindow wlw =
+ new StartingSurfaceDrawer.WindowlessStartingWindow(
+ runningTaskInfo.configuration, rootSurface);
+ final SurfaceControlViewHost mViewHost = new SurfaceControlViewHost(
+ mContext, display, wlw, "WindowlessSnapshotWindowCreator");
+ final Point taskSize = snapshot.getTaskSize();
+ final Rect snapshotBounds = new Rect(0, 0, taskSize.x, taskSize.y);
+ final Rect windowBounds = runningTaskInfo.configuration.windowConfiguration.getBounds();
+ final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState;
+ final FrameLayout rootLayout = new FrameLayout(
+ mSplashscreenContentDrawer.createViewContextWrapper(mContext));
+ mViewHost.setView(rootLayout, lp);
+ SnapshotDrawerUtils.drawSnapshotOnSurface(info, lp, wlw.mChildSurface, snapshot,
+ snapshotBounds, windowBounds, topWindowInsetsState, false /* releaseAfterDraw */);
+
+ final ActivityManager.TaskDescription taskDescription =
+ SnapshotDrawerUtils.getOrCreateTaskDescription(runningTaskInfo);
+
+ final SnapshotWindowRecord record = new SnapshotWindowRecord(mViewHost, wlw.mChildSurface,
+ taskDescription.getBackgroundColor(), snapshot.hasImeSurface(),
+ runningTaskInfo.topActivityType, removeExecutor);
+ mStartingWindowRecordManager.addRecord(taskId, record);
+ info.notifyAddComplete(wlw.mChildSurface);
+ }
+
+ private class SnapshotWindowRecord extends StartingSurfaceDrawer.SnapshotRecord {
+ private SurfaceControlViewHost mViewHost;
+ private SurfaceControl mChildSurface;
+ private final boolean mHasImeSurface;
+
+ SnapshotWindowRecord(SurfaceControlViewHost viewHost, SurfaceControl childSurface,
+ int bgColor, boolean hasImeSurface, int activityType,
+ ShellExecutor removeExecutor) {
+ super(activityType, removeExecutor);
+ mViewHost = viewHost;
+ mChildSurface = childSurface;
+ mBGColor = bgColor;
+ mHasImeSurface = hasImeSurface;
+ }
+
+ @Override
+ protected void removeImmediately() {
+ super.removeImmediately();
+ fadeoutThenRelease();
+ }
+
+ void fadeoutThenRelease() {
+ final ValueAnimator fadeOutAnimator = ValueAnimator.ofFloat(1f, 0f);
+ fadeOutAnimator.setDuration(DEFAULT_FADEOUT_DURATION);
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
+ fadeOutAnimator.addUpdateListener(animation -> {
+ if (mChildSurface == null || !mChildSurface.isValid()) {
+ fadeOutAnimator.cancel();
+ return;
+ }
+ t.setAlpha(mChildSurface, (float) animation.getAnimatedValue());
+ t.apply();
+ });
+
+ fadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (mChildSurface == null || !mChildSurface.isValid()) {
+ fadeOutAnimator.cancel();
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTransactionPool.release(t);
+ if (mChildSurface != null) {
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
+ t.remove(mChildSurface).apply();
+ mTransactionPool.release(t);
+ mChildSurface = null;
+ }
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+ }
+ });
+ fadeOutAnimator.start();
+ }
+
+ @Override
+ protected boolean hasImeSurface() {
+ return mHasImeSurface;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
new file mode 100644
index 0000000..12a0d40
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 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.startingsurface;
+
+import static android.graphics.Color.WHITE;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.Binder;
+import android.os.SystemClock;
+import android.view.Display;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.window.SplashScreenView;
+import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+
+class WindowlessSplashWindowCreator extends AbsSplashWindowCreator {
+
+ private final TransactionPool mTransactionPool;
+
+ WindowlessSplashWindowCreator(SplashscreenContentDrawer contentDrawer,
+ Context context,
+ ShellExecutor splashScreenExecutor,
+ DisplayManager displayManager,
+ StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager,
+ TransactionPool pool) {
+ super(contentDrawer, context, splashScreenExecutor, displayManager,
+ startingWindowRecordManager);
+ mTransactionPool = pool;
+ }
+
+ void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, SurfaceControl rootSurface) {
+ final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
+ final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
+ ? windowInfo.targetActivityInfo
+ : taskInfo.topActivityInfo;
+ if (activityInfo == null || activityInfo.packageName == null) {
+ return;
+ }
+
+ final int displayId = taskInfo.displayId;
+ final Display display = mDisplayManager.getDisplay(displayId);
+ if (display == null) {
+ // Can't show splash screen on requested display, so skip showing at all.
+ return;
+ }
+ final Context myContext = SplashscreenContentDrawer.createContext(mContext, windowInfo,
+ 0 /* theme */, STARTING_WINDOW_TYPE_SPLASH_SCREEN, mDisplayManager);
+ if (myContext == null) {
+ return;
+ }
+ final StartingSurfaceDrawer.WindowlessStartingWindow wlw =
+ new StartingSurfaceDrawer.WindowlessStartingWindow(
+ taskInfo.configuration, rootSurface);
+ final SurfaceControlViewHost viewHost = new SurfaceControlViewHost(
+ myContext, display, wlw, "WindowlessSplashWindowCreator");
+ final String title = "Windowless Splash " + taskInfo.taskId;
+ final WindowManager.LayoutParams lp = SplashscreenContentDrawer.createLayoutParameters(
+ myContext, windowInfo, STARTING_WINDOW_TYPE_SPLASH_SCREEN, title,
+ PixelFormat.TRANSLUCENT, new Binder());
+ final Rect windowBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ lp.width = windowBounds.width();
+ lp.height = windowBounds.height();
+ final ActivityManager.TaskDescription taskDescription;
+ if (taskInfo.taskDescription != null) {
+ taskDescription = taskInfo.taskDescription;
+ } else {
+ taskDescription = new ActivityManager.TaskDescription();
+ taskDescription.setBackgroundColor(WHITE);
+ }
+
+ final FrameLayout rootLayout = new FrameLayout(
+ mSplashscreenContentDrawer.createViewContextWrapper(mContext));
+ viewHost.setView(rootLayout, lp);
+
+ final int bgColor = taskDescription.getBackgroundColor();
+ final SplashScreenView splashScreenView = mSplashscreenContentDrawer
+ .makeSimpleSplashScreenContentView(myContext, windowInfo, bgColor);
+ rootLayout.addView(splashScreenView);
+ final SplashWindowRecord record = new SplashWindowRecord(viewHost, splashScreenView,
+ wlw.mChildSurface, bgColor);
+ mStartingWindowRecordManager.addRecord(taskInfo.taskId, record);
+ windowInfo.notifyAddComplete(wlw.mChildSurface);
+ }
+
+ private class SplashWindowRecord extends StartingSurfaceDrawer.StartingWindowRecord {
+ private SurfaceControlViewHost mViewHost;
+ private final long mCreateTime;
+ private SurfaceControl mChildSurface;
+ private final SplashScreenView mSplashView;
+
+ SplashWindowRecord(SurfaceControlViewHost viewHost, SplashScreenView splashView,
+ SurfaceControl childSurface, int bgColor) {
+ mViewHost = viewHost;
+ mSplashView = splashView;
+ mChildSurface = childSurface;
+ mBGColor = bgColor;
+ mCreateTime = SystemClock.uptimeMillis();
+ }
+
+ @Override
+ public void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
+ if (!immediately) {
+ mSplashscreenContentDrawer.applyExitAnimation(mSplashView,
+ info.windowAnimationLeash, info.mainFrame,
+ this::release, mCreateTime, 0 /* roundedCornerRadius */);
+ } else {
+ release();
+ }
+ }
+
+ void release() {
+ if (mChildSurface != null) {
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
+ t.remove(mChildSurface).apply();
+ mTransactionPool.release(t);
+ mChildSurface = null;
+ }
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
index bb43d7c..72fc8686 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
@@ -22,6 +22,7 @@
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_WINDOWLESS;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_DRAWN;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT;
@@ -30,6 +31,7 @@
import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS;
import android.window.StartingWindowInfo;
@@ -55,6 +57,7 @@
final boolean legacySplashScreen =
((parameter & TYPE_PARAMETER_LEGACY_SPLASH_SCREEN) != 0);
final boolean activityDrawn = (parameter & TYPE_PARAMETER_ACTIVITY_DRAWN) != 0;
+ final boolean windowlessSurface = (parameter & TYPE_PARAMETER_WINDOWLESS) != 0;
final boolean topIsHome = windowInfo.taskInfo.topActivityType == ACTIVITY_TYPE_HOME;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
@@ -67,10 +70,15 @@
+ "isSolidColorSplashScreen=%b, "
+ "legacySplashScreen=%b, "
+ "activityDrawn=%b, "
+ + "windowless=%b, "
+ "topIsHome=%b",
newTask, taskSwitch, processRunning, allowTaskSnapshot, activityCreated,
- isSolidColorSplashScreen, legacySplashScreen, activityDrawn, topIsHome);
+ isSolidColorSplashScreen, legacySplashScreen, activityDrawn, windowlessSurface,
+ topIsHome);
+ if (windowlessSurface) {
+ return STARTING_WINDOW_TYPE_WINDOWLESS;
+ }
if (!topIsHome) {
if (!processRunning || newTask || (taskSwitch && !activityCreated)) {
return getSplashscreenType(isSolidColorSplashScreen, legacySplashScreen);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 155990a..27b82c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -79,7 +79,24 @@
import java.util.ArrayList;
import java.util.Arrays;
-/** Plays transition animations */
+/**
+ * Plays transition animations. Within this player, each transition has a lifecycle.
+ * 1. When a transition is directly started or requested, it is added to "pending" state.
+ * 2. Once WMCore applies the transition and notifies, the transition moves to "ready" state.
+ * 3. When a transition starts animating, it is moved to the "active" state.
+ *
+ * Basically: --start--> PENDING --onTransitionReady--> READY --play--> ACTIVE --finish--> |
+ * --merge--> MERGED --^
+ *
+ * At the moment, only one transition can be animating at a time. While a transition is animating,
+ * transitions will be queued in the "ready" state for their turn. At the same time, whenever a
+ * transition makes it to the head of the "ready" queue, it will attempt to merge to with the
+ * "active" transition. If the merge succeeds, it will be moved to the "active" transition's
+ * "merged" and then the next "ready" transition can attempt to merge.
+ *
+ * Once the "active" transition animation is finished, it will be removed from the "active" list
+ * and then the next "ready" transition can play.
+ */
public class Transitions implements RemoteCallable<Transitions> {
static final String TAG = "ShellTransitions";
@@ -150,14 +167,22 @@
private static final class ActiveTransition {
IBinder mToken;
TransitionHandler mHandler;
- boolean mMerged;
boolean mAborted;
TransitionInfo mInfo;
SurfaceControl.Transaction mStartT;
SurfaceControl.Transaction mFinishT;
+
+ /** Ordered list of transitions which have been merged into this one. */
+ private ArrayList<ActiveTransition> mMerged;
}
- /** Keeps track of currently playing transitions in the order of receipt. */
+ /** Keeps track of transitions which have been started, but aren't ready yet. */
+ private final ArrayList<ActiveTransition> mPendingTransitions = new ArrayList<>();
+
+ /** Keeps track of transitions which are ready to play but still waiting for their turn. */
+ private final ArrayList<ActiveTransition> mReadyTransitions = new ArrayList<>();
+
+ /** Keeps track of currently playing transitions. For now, there can only be 1 max. */
private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();
public Transitions(@NonNull Context context,
@@ -322,7 +347,8 @@
* will be executed when the last active transition is finished.
*/
public void runOnIdle(Runnable runnable) {
- if (mActiveTransitions.isEmpty()) {
+ if (mActiveTransitions.isEmpty() && mPendingTransitions.isEmpty()
+ && mReadyTransitions.isEmpty()) {
runnable.run();
} else {
mRunWhenIdleQueue.add(runnable);
@@ -441,9 +467,9 @@
}
}
- private int findActiveTransition(IBinder token) {
- for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
- if (mActiveTransitions.get(i).mToken == token) return i;
+ private static int findByToken(ArrayList<ActiveTransition> list, IBinder token) {
+ for (int i = list.size() - 1; i >= 0; --i) {
+ if (list.get(i).mToken == token) return i;
}
return -1;
}
@@ -481,14 +507,24 @@
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
transitionToken, info);
- final int activeIdx = findActiveTransition(transitionToken);
+ final int activeIdx = findByToken(mPendingTransitions, transitionToken);
if (activeIdx < 0) {
- throw new IllegalStateException("Got transitionReady for non-active transition "
+ throw new IllegalStateException("Got transitionReady for non-pending transition "
+ transitionToken + ". expecting one of "
- + Arrays.toString(mActiveTransitions.stream().map(
+ + Arrays.toString(mPendingTransitions.stream().map(
activeTransition -> activeTransition.mToken).toArray()));
}
- final ActiveTransition active = mActiveTransitions.get(activeIdx);
+ if (activeIdx > 0) {
+ Log.e(TAG, "Transition became ready out-of-order " + transitionToken + ". Expected"
+ + " order: " + Arrays.toString(mPendingTransitions.stream().map(
+ activeTransition -> activeTransition.mToken).toArray()));
+ }
+ // Move from pending to ready
+ final ActiveTransition active = mPendingTransitions.remove(activeIdx);
+ mReadyTransitions.add(active);
+ active.mInfo = info;
+ active.mStartT = t;
+ active.mFinishT = finishT;
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT);
@@ -496,9 +532,6 @@
if (info.getType() == TRANSIT_SLEEP) {
if (activeIdx > 0) {
- active.mInfo = info;
- active.mStartT = t;
- active.mFinishT = finishT;
if (!info.getRootLeash().isValid()) {
// Shell has some debug settings which makes calling binders with invalid
// surfaces crash, so replace it with a "real" one.
@@ -518,9 +551,7 @@
// housekeeping and return.
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s",
transitionToken, info);
- t.apply();
- finishT.apply();
- onAbort(transitionToken);
+ onAbort(active);
return;
}
@@ -546,40 +577,90 @@
// changes are underneath another change.
|| ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
&& allOccluded)) {
- t.apply();
- finishT.apply();
// Treat this as an abort since we are bypassing any merge logic and effectively
// finishing immediately.
- onAbort(transitionToken);
- releaseSurfaces(info);
+ onAbort(active);
return;
}
- active.mInfo = info;
- active.mStartT = t;
- active.mFinishT = finishT;
setupStartState(active.mInfo, active.mStartT, active.mFinishT);
- if (activeIdx > 0) {
- // This is now playing at the same time as an existing animation, so try merging it.
- attemptMergeTransition(mActiveTransitions.get(0), active);
+ if (mReadyTransitions.size() > 1) {
+ // There are already transitions waiting in the queue, so just return.
return;
}
- // The normal case, just play it.
- playTransition(active);
+ processReadyQueue();
}
- /**
- * Attempt to merge by delegating the transition start to the handler of the currently
- * playing transition.
- */
- void attemptMergeTransition(@NonNull ActiveTransition playing,
- @NonNull ActiveTransition merging) {
+ void processReadyQueue() {
+ if (mReadyTransitions.isEmpty()) {
+ // Check if idle.
+ if (mActiveTransitions.isEmpty() && mPendingTransitions.isEmpty()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition "
+ + "animations finished");
+ // Run all runnables from the run-when-idle queue.
+ for (int i = 0; i < mRunWhenIdleQueue.size(); i++) {
+ mRunWhenIdleQueue.get(i).run();
+ }
+ mRunWhenIdleQueue.clear();
+ }
+ return;
+ }
+ final ActiveTransition ready = mReadyTransitions.get(0);
+ if (mActiveTransitions.isEmpty()) {
+ // The normal case, just play it (currently we only support 1 active transition).
+ mReadyTransitions.remove(0);
+ mActiveTransitions.add(ready);
+ if (ready.mAborted) {
+ // finish now since there's nothing to animate. Calls back into processReadyQueue
+ onFinish(ready, null, null);
+ return;
+ }
+ playTransition(ready);
+ // Attempt to merge any more queued-up transitions.
+ processReadyQueue();
+ return;
+ }
+ // An existing animation is playing, so see if we can merge.
+ final ActiveTransition playing = mActiveTransitions.get(0);
+ if (ready.mAborted) {
+ // record as merged since it is no-op. Calls back into processReadyQueue
+ onMerged(playing, ready);
+ return;
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
+ " another transition %s is still animating. Notify the animating transition"
- + " in case they can be merged", merging.mToken, playing.mToken);
- playing.mHandler.mergeAnimation(merging.mToken, merging.mInfo, merging.mStartT,
- playing.mToken, (wct, cb) -> onFinish(merging.mToken, wct, cb));
+ + " in case they can be merged", ready.mToken, playing.mToken);
+ playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
+ playing.mToken, (wct, cb) -> onMerged(playing, ready));
+ }
+
+ private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged %s",
+ merged.mToken);
+ int readyIdx = 0;
+ if (mReadyTransitions.isEmpty() || mReadyTransitions.get(0) != merged) {
+ Log.e(TAG, "Merged transition out-of-order?");
+ readyIdx = mReadyTransitions.indexOf(merged);
+ if (readyIdx < 0) {
+ Log.e(TAG, "Merged a transition that is no-longer queued?");
+ return;
+ }
+ }
+ mReadyTransitions.remove(readyIdx);
+ if (playing.mMerged == null) {
+ playing.mMerged = new ArrayList<>();
+ }
+ playing.mMerged.add(merged);
+ // if it was aborted, then onConsumed has already been reported.
+ if (merged.mHandler != null && !merged.mAborted) {
+ merged.mHandler.onTransitionConsumed(merged.mToken, false /* abort */, merged.mFinishT);
+ }
+ for (int i = 0; i < mObservers.size(); ++i) {
+ mObservers.get(i).onTransitionMerged(merged.mToken, playing.mToken);
+ }
+ // See if we should merge another transition.
+ processReadyQueue();
}
private void playTransition(@NonNull ActiveTransition active) {
@@ -594,7 +675,7 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
active.mHandler);
boolean consumed = active.mHandler.startAnimation(active.mToken, active.mInfo,
- active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb));
+ active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active, wct, cb));
if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
return;
@@ -602,7 +683,7 @@
}
// Otherwise give every other handler a chance
active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT,
- active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb), active.mHandler);
+ active.mFinishT, (wct, cb) -> onFinish(active, wct, cb), active.mHandler);
}
/**
@@ -645,15 +726,28 @@
return null;
}
- /** Special version of finish just for dealing with no-op/invalid transitions. */
- private void onAbort(IBinder transition) {
- onFinish(transition, null /* wct */, null /* wctCB */, true /* abort */);
- }
+ /** Aborts a transition. This will still queue it up to maintain order. */
+ private void onAbort(ActiveTransition transition) {
+ // apply immediately since they may be "parallel" operations: We currently we use abort for
+ // thing which are independent to other transitions (like starting-window transfer).
+ transition.mStartT.apply();
+ transition.mFinishT.apply();
+ transition.mAborted = true;
- private void onFinish(IBinder transition,
- @Nullable WindowContainerTransaction wct,
- @Nullable WindowContainerTransactionCallback wctCB) {
- onFinish(transition, wct, wctCB, false /* abort */);
+ if (transition.mHandler != null) {
+ // Notifies to clean-up the aborted transition.
+ transition.mHandler.onTransitionConsumed(
+ transition.mToken, true /* aborted */, null /* finishTransaction */);
+ }
+
+ releaseSurfaces(transition.mInfo);
+
+ // This still went into the queue (to maintain the correct finish ordering).
+ if (mReadyTransitions.size() > 1) {
+ // There are already transitions waiting in the queue, so just return.
+ return;
+ }
+ processReadyQueue();
}
/**
@@ -665,167 +759,97 @@
info.releaseAnimSurfaces();
}
- private void onFinish(IBinder transition,
+ private void onFinish(ActiveTransition active,
@Nullable WindowContainerTransaction wct,
- @Nullable WindowContainerTransactionCallback wctCB,
- boolean abort) {
- int activeIdx = findActiveTransition(transition);
+ @Nullable WindowContainerTransactionCallback wctCB) {
+ int activeIdx = mActiveTransitions.indexOf(active);
if (activeIdx < 0) {
Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
- + " a handler didn't properly deal with a merge.", new RuntimeException());
+ + " a handler didn't properly deal with a merge. " + active.mToken,
+ new RuntimeException());
return;
- } else if (activeIdx > 0) {
- // This transition was merged.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged (abort=%b:"
- + " %s", abort, transition);
- final ActiveTransition active = mActiveTransitions.get(activeIdx);
- active.mMerged = true;
- active.mAborted = abort;
- if (active.mHandler != null) {
- active.mHandler.onTransitionConsumed(
- active.mToken, abort, abort ? null : active.mFinishT);
- }
- for (int i = 0; i < mObservers.size(); ++i) {
- mObservers.get(i).onTransitionMerged(
- active.mToken, mActiveTransitions.get(0).mToken);
- }
- return;
+ } else if (activeIdx != 0) {
+ // Relevant right now since we only allow 1 active transition at a time.
+ Log.e(TAG, "Finishing a transition out of order. " + active.mToken);
}
- final ActiveTransition active = mActiveTransitions.get(activeIdx);
- active.mAborted = abort;
- if (active.mAborted && active.mHandler != null) {
- // Notifies to clean-up the aborted transition.
- active.mHandler.onTransitionConsumed(
- transition, true /* aborted */, null /* finishTransaction */);
- }
+ mActiveTransitions.remove(activeIdx);
+
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
}
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Transition animation finished (abort=%b), notifying core %s", abort, transition);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animation finished "
+ + "(aborted=%b), notifying core %s", active.mAborted, active.mToken);
if (active.mStartT != null) {
// Applied by now, so clear immediately to remove any references. Do not set to null
// yet, though, since nullness is used later to disambiguate malformed transitions.
active.mStartT.clear();
}
- // Merge all relevant transactions together
+ // Merge all associated transactions together
SurfaceControl.Transaction fullFinish = active.mFinishT;
- for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
- final ActiveTransition toMerge = mActiveTransitions.get(iA);
- if (!toMerge.mMerged) break;
- // Include start. It will be a no-op if it was already applied. Otherwise, we need it
- // to maintain consistent state.
- if (toMerge.mStartT != null) {
- if (fullFinish == null) {
- fullFinish = toMerge.mStartT;
- } else {
- fullFinish.merge(toMerge.mStartT);
+ if (active.mMerged != null) {
+ for (int iM = 0; iM < active.mMerged.size(); ++iM) {
+ final ActiveTransition toMerge = active.mMerged.get(iM);
+ // Include start. It will be a no-op if it was already applied. Otherwise, we need
+ // it to maintain consistent state.
+ if (toMerge.mStartT != null) {
+ if (fullFinish == null) {
+ fullFinish = toMerge.mStartT;
+ } else {
+ fullFinish.merge(toMerge.mStartT);
+ }
}
- }
- if (toMerge.mFinishT != null) {
- if (fullFinish == null) {
- fullFinish = toMerge.mFinishT;
- } else {
- fullFinish.merge(toMerge.mFinishT);
+ if (toMerge.mFinishT != null) {
+ if (fullFinish == null) {
+ fullFinish = toMerge.mFinishT;
+ } else {
+ fullFinish.merge(toMerge.mFinishT);
+ }
}
}
}
if (fullFinish != null) {
fullFinish.apply();
}
- // Now perform all the finishes.
+ // Now perform all the finish callbacks (starting with the playing one and then all the
+ // transitions merged into it).
releaseSurfaces(active.mInfo);
- mActiveTransitions.remove(activeIdx);
- mOrganizer.finishTransition(transition, wct, wctCB);
- while (activeIdx < mActiveTransitions.size()) {
- if (!mActiveTransitions.get(activeIdx).mMerged) break;
- ActiveTransition merged = mActiveTransitions.remove(activeIdx);
- mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
- releaseSurfaces(merged.mInfo);
- }
- // sift through aborted transitions
- while (mActiveTransitions.size() > activeIdx
- && mActiveTransitions.get(activeIdx).mAborted) {
- ActiveTransition aborted = mActiveTransitions.remove(activeIdx);
- // Notifies to clean-up the aborted transition.
- if (aborted.mHandler != null) {
- aborted.mHandler.onTransitionConsumed(
- transition, true /* aborted */, null /* finishTransaction */);
+ mOrganizer.finishTransition(active.mToken, wct, wctCB);
+ if (active.mMerged != null) {
+ for (int iM = 0; iM < active.mMerged.size(); ++iM) {
+ ActiveTransition merged = active.mMerged.get(iM);
+ mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
+ releaseSurfaces(merged.mInfo);
}
- mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
- for (int i = 0; i < mObservers.size(); ++i) {
- mObservers.get(i).onTransitionFinished(aborted.mToken, true);
- }
- releaseSurfaces(aborted.mInfo);
- }
- if (mActiveTransitions.size() <= activeIdx) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
- + "finished");
- // Run all runnables from the run-when-idle queue.
- for (int i = 0; i < mRunWhenIdleQueue.size(); i++) {
- mRunWhenIdleQueue.get(i).run();
- }
- mRunWhenIdleQueue.clear();
- return;
- }
- // Start animating the next active transition
- final ActiveTransition next = mActiveTransitions.get(activeIdx);
- if (next.mInfo == null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transition after one"
- + " finished, but it isn't ready yet.");
- return;
- }
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transitions after one"
- + " finished, so start the next one.");
- playTransition(next);
- // Now try to merge the rest of the transitions (re-acquire activeIdx since next may have
- // finished immediately)
- activeIdx = findActiveTransition(next.mToken);
- if (activeIdx < 0) {
- // This means 'next' finished immediately and thus re-entered this function. Since
- // that is the case, just return here since all relevant logic has already run in the
- // re-entered call.
- return;
+ active.mMerged.clear();
}
- // This logic is also convoluted because 'next' may finish immediately in response to any of
- // the merge requests (eg. if it decided to "cancel" itself).
- int mergeIdx = activeIdx + 1;
- while (mergeIdx < mActiveTransitions.size()) {
- ActiveTransition mergeCandidate = mActiveTransitions.get(mergeIdx);
- if (mergeCandidate.mAborted) {
- // transition was aborted, so we can skip for now (still leave it in the list
- // so that it gets cleaned-up in the right order).
- ++mergeIdx;
- continue;
- }
- if (mergeCandidate.mInfo == null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition merge candidate"
- + " %s is not ready yet", mergeCandidate.mToken);
- // The later transition should not be merged if the prior one is not ready.
- return;
- }
- if (mergeCandidate.mMerged) {
- throw new IllegalStateException("Can't merge a transition after not-merging"
- + " a preceding one.");
- }
- attemptMergeTransition(next, mergeCandidate);
- mergeIdx = findActiveTransition(mergeCandidate.mToken);
- if (mergeIdx < 0) {
- // This means 'next' finished immediately and thus re-entered this function. Since
- // that is the case, just return here since all relevant logic has already run in
- // the re-entered call.
- return;
- }
- ++mergeIdx;
+ // Now that this is done, check the ready queue for more work.
+ processReadyQueue();
+ }
+
+ private boolean isTransitionKnown(IBinder token) {
+ for (int i = 0; i < mPendingTransitions.size(); ++i) {
+ if (mPendingTransitions.get(i).mToken == token) return true;
}
+ for (int i = 0; i < mReadyTransitions.size(); ++i) {
+ if (mReadyTransitions.get(i).mToken == token) return true;
+ }
+ for (int i = 0; i < mActiveTransitions.size(); ++i) {
+ final ActiveTransition active = mActiveTransitions.get(i);
+ if (active.mToken == token) return true;
+ if (active.mMerged == null) continue;
+ for (int m = 0; m < active.mMerged.size(); ++m) {
+ if (active.mMerged.get(m).mToken == token) return true;
+ }
+ }
+ return false;
}
void requestStartTransition(@NonNull IBinder transitionToken,
@Nullable TransitionRequestInfo request) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s",
transitionToken, request);
- if (findActiveTransition(transitionToken) >= 0) {
+ if (isTransitionKnown(transitionToken)) {
throw new RuntimeException("Transition already started " + transitionToken);
}
final ActiveTransition active = new ActiveTransition();
@@ -858,15 +882,13 @@
}
mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
active.mToken = transitionToken;
- int insertIdx = 0;
- for (; insertIdx < mActiveTransitions.size(); ++insertIdx) {
- if (mActiveTransitions.get(insertIdx).mInfo == null) {
- // A `startNewTransition` was sent to WMCore, but wasn't acknowledged before WMCore
- // made this request, so insert this request beforehand to keep order in sync.
- break;
- }
- }
- mActiveTransitions.add(insertIdx, active);
+ // Currently, WMCore only does one transition at a time. If it makes a requestStart, it
+ // is already collecting that transition on core-side, so it will be the next one to
+ // become ready. There may already be pending transitions added as part of direct
+ // `startNewTransition` but if we have a request now, it means WM created the request
+ // transition before it acknowledged any of the pending `startNew` transitions. So, insert
+ // it at the front.
+ mPendingTransitions.add(0, active);
}
/** Start a new transition directly. */
@@ -875,7 +897,7 @@
final ActiveTransition active = new ActiveTransition();
active.mHandler = handler;
active.mToken = mOrganizer.startNewTransition(type, wct);
- mActiveTransitions.add(active);
+ mPendingTransitions.add(active);
return active.mToken;
}
@@ -894,27 +916,38 @@
* @param forceFinish When non-null, this is the transition that we last sent the SLEEP merge
* signal to -- so it will be force-finished if it's still running.
*/
- private void finishForSleep(@Nullable IBinder forceFinish) {
- if (mActiveTransitions.isEmpty() || mSleepHandler.mSleepTransitions.isEmpty()) {
+ private void finishForSleep(@Nullable ActiveTransition forceFinish) {
+ if ((mActiveTransitions.isEmpty() && mReadyTransitions.isEmpty())
+ || mSleepHandler.mSleepTransitions.isEmpty()) {
+ // Done finishing things.
+ // Prevent any weird leaks... shouldn't happen though.
+ mSleepHandler.mSleepTransitions.clear();
return;
}
- if (forceFinish != null && mActiveTransitions.get(0).mToken == forceFinish) {
+ if (forceFinish != null && mActiveTransitions.contains(forceFinish)) {
Log.e(TAG, "Forcing transition to finish due to sleep timeout: "
- + mActiveTransitions.get(0).mToken);
- onFinish(mActiveTransitions.get(0).mToken, null, null, true);
+ + forceFinish.mToken);
+ forceFinish.mAborted = true;
+ // Last notify of it being consumed. Note: mHandler should never be null,
+ // but check just to be safe.
+ if (forceFinish.mHandler != null) {
+ forceFinish.mHandler.onTransitionConsumed(
+ forceFinish.mToken, true /* aborted */, null /* finishTransaction */);
+ }
+ onFinish(forceFinish, null, null);
}
final SurfaceControl.Transaction dummyT = new SurfaceControl.Transaction();
while (!mActiveTransitions.isEmpty() && !mSleepHandler.mSleepTransitions.isEmpty()) {
final ActiveTransition playing = mActiveTransitions.get(0);
- int sleepIdx = findActiveTransition(mSleepHandler.mSleepTransitions.get(0));
+ int sleepIdx = findByToken(mReadyTransitions, mSleepHandler.mSleepTransitions.get(0));
if (sleepIdx >= 0) {
// Try to signal that we are sleeping by attempting to merge the sleep transition
// into the playing one.
- final ActiveTransition nextSleep = mActiveTransitions.get(sleepIdx);
+ final ActiveTransition nextSleep = mReadyTransitions.get(sleepIdx);
playing.mHandler.mergeAnimation(nextSleep.mToken, nextSleep.mInfo, dummyT,
playing.mToken, (wct, cb) -> {});
} else {
- Log.e(TAG, "Couldn't find sleep transition in active list: "
+ Log.e(TAG, "Couldn't find sleep transition in ready list: "
+ mSleepHandler.mSleepTransitions.get(0));
}
// it's possible to complete immediately. If that happens, just repeat the signal
@@ -922,8 +955,7 @@
// finishing immediately.
if (!mActiveTransitions.isEmpty() && mActiveTransitions.get(0) == playing) {
// Give it a (very) short amount of time to process it before forcing.
- mMainExecutor.executeDelayed(
- () -> finishForSleep(playing.mToken), SLEEP_ALLOWANCE_MS);
+ mMainExecutor.executeDelayed(() -> finishForSleep(playing), SLEEP_ALLOWANCE_MS);
break;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 5a4a44f..6dae479 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -422,7 +422,7 @@
RemoteAnimationTarget animationTarget = createAnimationTarget();
RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget};
if (mController.mBackAnimationAdapter != null) {
- mController.mBackAnimationAdapter.getRunner().onAnimationStart(type,
+ mController.mBackAnimationAdapter.getRunner().onAnimationStart(
targets, null, null, mBackAnimationFinishedCallback);
mShellExecutor.flushAll();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
new file mode 100644
index 0000000..2814ef9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 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.back;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.WindowConfiguration;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Choreographer;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.window.BackNavigationInfo;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class CustomizeActivityAnimationTest extends ShellTestCase {
+ private static final int BOUND_SIZE = 100;
+ @Mock
+ private BackAnimationBackground mBackAnimationBackground;
+ @Mock
+ private Animation mMockCloseAnimation;
+ @Mock
+ private Animation mMockOpenAnimation;
+
+ private CustomizeActivityAnimation mCustomizeActivityAnimation;
+
+ @Before
+ public void setUp() throws Exception {
+ mCustomizeActivityAnimation = new CustomizeActivityAnimation(mContext,
+ mBackAnimationBackground, mock(SurfaceControl.Transaction.class),
+ mock(Choreographer.class));
+ spyOn(mCustomizeActivityAnimation);
+ spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
+ doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
+ .load(any(), eq(false));
+ doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
+ .load(any(), eq(true));
+ }
+
+ RemoteAnimationTarget createAnimationTarget(boolean open) {
+ SurfaceControl topWindowLeash = new SurfaceControl();
+ return new RemoteAnimationTarget(1,
+ open ? RemoteAnimationTarget.MODE_OPENING : RemoteAnimationTarget.MODE_CLOSING,
+ topWindowLeash, false, new Rect(), new Rect(), -1,
+ new Point(0, 0), new Rect(0, 0, BOUND_SIZE, BOUND_SIZE), new Rect(),
+ new WindowConfiguration(), true, null, null, null, false, -1);
+ }
+
+ @Test
+ public void receiveFinishAfterInvoke() throws InterruptedException {
+ mCustomizeActivityAnimation.prepareNextAnimation(
+ new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
+ final RemoteAnimationTarget close = createAnimationTarget(false);
+ final RemoteAnimationTarget open = createAnimationTarget(true);
+ // start animation with remote animation targets
+ final CountDownLatch finishCalled = new CountDownLatch(1);
+ final Runnable finishCallback = finishCalled::countDown;
+ mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
+ new RemoteAnimationTarget[]{close, open}, null, null, finishCallback);
+ verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
+ eq(BOUND_SIZE), eq(BOUND_SIZE));
+ verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
+ eq(BOUND_SIZE), eq(BOUND_SIZE));
+
+ try {
+ mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
+ } catch (RemoteException r) {
+ fail("onBackInvoked throw remote exception");
+ }
+ verify(mCustomizeActivityAnimation).onGestureCommitted();
+ finishCalled.await(1, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void receiveFinishAfterCancel() throws InterruptedException {
+ mCustomizeActivityAnimation.prepareNextAnimation(
+ new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
+ final RemoteAnimationTarget close = createAnimationTarget(false);
+ final RemoteAnimationTarget open = createAnimationTarget(true);
+ // start animation with remote animation targets
+ final CountDownLatch finishCalled = new CountDownLatch(1);
+ final Runnable finishCallback = finishCalled::countDown;
+ mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
+ new RemoteAnimationTarget[]{close, open}, null, null, finishCallback);
+ verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
+ eq(BOUND_SIZE), eq(BOUND_SIZE));
+ verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
+ eq(BOUND_SIZE), eq(BOUND_SIZE));
+
+ try {
+ mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackCancelled();
+ } catch (RemoteException r) {
+ fail("onBackCancelled throw remote exception");
+ }
+ finishCalled.await(1, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void receiveFinishWithoutAnimationAfterInvoke() throws InterruptedException {
+ mCustomizeActivityAnimation.prepareNextAnimation(
+ new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
+ // start animation without any remote animation targets
+ final CountDownLatch finishCalled = new CountDownLatch(1);
+ final Runnable finishCallback = finishCalled::countDown;
+ mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
+ new RemoteAnimationTarget[]{}, null, null, finishCallback);
+
+ try {
+ mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
+ } catch (RemoteException r) {
+ fail("onBackInvoked throw remote exception");
+ }
+ verify(mCustomizeActivityAnimation).onGestureCommitted();
+ finishCalled.await(1, TimeUnit.SECONDS);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
new file mode 100644
index 0000000..96d202c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2023 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.common;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_CLOSED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_OPENED;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Surface;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link TabletopModeController}.
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class TabletopModeControllerTest extends ShellTestCase {
+ // It's considered tabletop mode if the display rotation angle matches what's in this array.
+ // It's defined as com.android.internal.R.array.config_deviceTabletopRotations on real devices.
+ private static final int[] TABLETOP_MODE_ROTATIONS = new int[] {
+ 90 /* Surface.ROTATION_90 */,
+ 270 /* Surface.ROTATION_270 */
+ };
+
+ private TestShellExecutor mMainExecutor;
+
+ private Configuration mConfiguration;
+
+ private TabletopModeController mPipTabletopController;
+
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private ShellInit mShellInit;
+
+ @Mock
+ private Resources mResources;
+
+ @Mock
+ private DevicePostureController mDevicePostureController;
+
+ @Mock
+ private DisplayController mDisplayController;
+
+ @Mock
+ private TabletopModeController.OnTabletopModeChangedListener mOnTabletopModeChangedListener;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mResources.getIntArray(com.android.internal.R.array.config_deviceTabletopRotations))
+ .thenReturn(TABLETOP_MODE_ROTATIONS);
+ when(mContext.getResources()).thenReturn(mResources);
+ mMainExecutor = new TestShellExecutor();
+ mConfiguration = new Configuration();
+ mPipTabletopController = new TabletopModeController(mContext, mShellInit,
+ mDevicePostureController, mDisplayController, mMainExecutor);
+ mPipTabletopController.onInit();
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), eq(mPipTabletopController));
+ }
+
+ @Test
+ public void registerOnTabletopModeChangedListener_notInTabletopMode_callbackFalse() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.registerOnTabletopModeChangedListener(
+ mOnTabletopModeChangedListener);
+
+ verify(mOnTabletopModeChangedListener, times(1))
+ .onTabletopModeChanged(false);
+ }
+
+ @Test
+ public void registerOnTabletopModeChangedListener_inTabletopMode_callbackTrue() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.registerOnTabletopModeChangedListener(
+ mOnTabletopModeChangedListener);
+
+ verify(mOnTabletopModeChangedListener, times(1))
+ .onTabletopModeChanged(true);
+ }
+
+ @Test
+ public void registerOnTabletopModeChangedListener_notInTabletopModeTwice_callbackOnce() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.registerOnTabletopModeChangedListener(
+ mOnTabletopModeChangedListener);
+ clearInvocations(mOnTabletopModeChangedListener);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ verifyZeroInteractions(mOnTabletopModeChangedListener);
+ }
+
+ // Test cases starting from folded state (DEVICE_POSTURE_CLOSED)
+ @Test
+ public void foldedRotation90_halfOpen_scheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertTrue(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation0_halfOpen_noScheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation90_halfOpenThenUnfold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation90_halfOpenThenFold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation90_halfOpenThenRotate_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ // Test cases starting from unfolded state (DEVICE_POSTURE_OPENED)
+ @Test
+ public void unfoldedRotation90_halfOpen_scheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertTrue(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation0_halfOpen_noScheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation90_halfOpenThenUnfold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation90_halfOpenThenFold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation90_halfOpenThenRotate_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index 11fda8b..bf62acf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -24,8 +24,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MAX_ANIMATION_DURATION;
-import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MINIMAL_ANIMATION_DURATION;
+import static com.android.wm.shell.startingsurface.SplashscreenContentDrawer.MAX_ANIMATION_DURATION;
+import static com.android.wm.shell.startingsurface.SplashscreenContentDrawer.MINIMAL_ANIMATION_DURATION;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -56,11 +56,9 @@
import android.os.Looper;
import android.os.UserHandle;
import android.testing.TestableContext;
-import android.view.Display;
import android.view.IWindowSession;
import android.view.InsetsState;
import android.view.Surface;
-import android.view.View;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.WindowMetrics;
@@ -106,36 +104,7 @@
private ShellExecutor mTestExecutor;
private final TestableContext mTestContext = new TestContext(
InstrumentationRegistry.getInstrumentation().getTargetContext());
- TestStartingSurfaceDrawer mStartingSurfaceDrawer;
-
- static final class TestStartingSurfaceDrawer extends StartingSurfaceDrawer{
- int mAddWindowForTask = 0;
-
- TestStartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
- IconProvider iconProvider, TransactionPool pool) {
- super(context, splashScreenExecutor, iconProvider, pool);
- }
-
- @Override
- protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
- WindowManager.LayoutParams params, int suggestType) {
- // listen for addView
- mAddWindowForTask = taskId;
- saveSplashScreenRecord(appToken, taskId, view, suggestType);
- // Do not wait for background color
- return false;
- }
-
- @Override
- protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo,
- boolean immediately) {
- // listen for removeView
- if (mAddWindowForTask == removalInfo.taskId) {
- mAddWindowForTask = 0;
- }
- mStartingWindowRecords.remove(removalInfo.taskId);
- }
- }
+ StartingSurfaceDrawer mStartingSurfaceDrawer;
private static class TestContext extends TestableContext {
TestContext(Context context) {
@@ -165,44 +134,51 @@
doReturn(metrics).when(mMockWindowManager).getMaximumWindowMetrics();
doNothing().when(mMockWindowManager).addView(any(), any());
mTestExecutor = new HandlerExecutor(mTestHandler);
+ mStartingSurfaceDrawer = new StartingSurfaceDrawer(mTestContext, mTestExecutor,
+ mIconProvider, mTransactionPool);
mStartingSurfaceDrawer = spy(
- new TestStartingSurfaceDrawer(mTestContext, mTestExecutor, mIconProvider,
+ new StartingSurfaceDrawer(mTestContext, mTestExecutor, mIconProvider,
mTransactionPool));
+ spyOn(mStartingSurfaceDrawer.mSplashscreenWindowCreator);
+ spyOn(mStartingSurfaceDrawer.mWindowRecords);
+ spyOn(mStartingSurfaceDrawer.mWindowlessRecords);
}
@Test
public void testAddSplashScreenSurface() {
final int taskId = 1;
final StartingWindowInfo windowInfo =
- createWindowInfo(taskId, android.R.style.Theme);
- mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder,
+ createWindowInfo(taskId, android.R.style.Theme, mBinder);
+ mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo,
STARTING_WINDOW_TYPE_SPLASH_SCREEN);
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any(),
+ verify(mStartingSurfaceDrawer.mSplashscreenWindowCreator).addWindow(
+ eq(taskId), eq(mBinder), any(), any(), any(),
eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN));
- assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo();
removalInfo.taskId = windowInfo.taskInfo.taskId;
mStartingSurfaceDrawer.removeStartingWindow(removalInfo);
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer).removeWindowSynced(any(), eq(false));
- assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0);
+ verify(mStartingSurfaceDrawer.mWindowRecords).removeWindow(any(), eq(false));
+ assertEquals(mStartingSurfaceDrawer.mWindowRecords.recordSize(), 0);
}
@Test
public void testFallbackDefaultTheme() {
final int taskId = 1;
final StartingWindowInfo windowInfo =
- createWindowInfo(taskId, 0);
+ createWindowInfo(taskId, 0, mBinder);
final int[] theme = new int[1];
doAnswer(invocation -> theme[0] = (Integer) invocation.callRealMethod())
- .when(mStartingSurfaceDrawer).getSplashScreenTheme(eq(0), any());
+ .when(mStartingSurfaceDrawer.mSplashscreenWindowCreator)
+ .getSplashScreenTheme(eq(0), any());
- mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder,
+ mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo,
STARTING_WINDOW_TYPE_SPLASH_SCREEN);
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer).getSplashScreenTheme(eq(0), any());
+ verify(mStartingSurfaceDrawer.mSplashscreenWindowCreator)
+ .getSplashScreenTheme(eq(0), any());
assertNotEquals(theme[0], 0);
}
@@ -241,7 +217,7 @@
public void testRemoveTaskSnapshotWithImeSurfaceWhenOnImeDrawn() throws Exception {
final int taskId = 1;
final StartingWindowInfo windowInfo =
- createWindowInfo(taskId, android.R.style.Theme);
+ createWindowInfo(taskId, android.R.style.Theme, mBinder);
TaskSnapshot snapshot = createTaskSnapshot(100, 100, new Point(100, 100),
new Rect(0, 0, 0, 50), true /* hasImeSurface */);
final IWindowSession session = WindowManagerGlobal.getWindowSession();
@@ -270,7 +246,7 @@
when(TaskSnapshotWindow.create(eq(windowInfo), eq(mBinder), eq(snapshot), any(),
any())).thenReturn(mockSnapshotWindow);
// Simulate a task snapshot window created with IME snapshot shown.
- mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, mBinder, snapshot);
+ mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, snapshot);
waitHandlerIdle(mTestHandler);
// Verify the task snapshot with IME snapshot will be removed when received the real IME
@@ -278,27 +254,36 @@
// makeTaskSnapshotWindow shall call removeWindowSynced before there add a new
// StartingWindowRecord for the task.
mStartingSurfaceDrawer.onImeDrawnOnTask(1);
- verify(mStartingSurfaceDrawer, times(2))
- .removeWindowSynced(any(), eq(true));
+ verify(mStartingSurfaceDrawer.mWindowRecords, times(2))
+ .removeWindow(any(), eq(true));
}
}
@Test
public void testClearAllWindows() {
final int taskId = 1;
- final StartingWindowInfo windowInfo =
- createWindowInfo(taskId, android.R.style.Theme);
- mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder,
- STARTING_WINDOW_TYPE_SPLASH_SCREEN);
- waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any(),
- eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN));
- assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
+ mStartingSurfaceDrawer.mWindowRecords.addRecord(taskId,
+ new StartingSurfaceDrawer.StartingWindowRecord() {
+ @Override
+ public void removeIfPossible(StartingWindowRemovalInfo info,
+ boolean immediately) {
+ }
+ });
+ mStartingSurfaceDrawer.mWindowlessRecords.addRecord(taskId,
+ new StartingSurfaceDrawer.StartingWindowRecord() {
+ @Override
+ public void removeIfPossible(StartingWindowRemovalInfo info,
+ boolean immediately) {
+
+ }
+ });
mStartingSurfaceDrawer.clearAllWindows();
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer).removeWindowSynced(any(), eq(true));
- assertEquals(mStartingSurfaceDrawer.mStartingWindowRecords.size(), 0);
+ verify(mStartingSurfaceDrawer.mWindowRecords).removeWindow(any(), eq(true));
+ assertEquals(mStartingSurfaceDrawer.mWindowRecords.recordSize(), 0);
+ verify(mStartingSurfaceDrawer.mWindowlessRecords).removeWindow(any(), eq(true));
+ assertEquals(mStartingSurfaceDrawer.mWindowlessRecords.recordSize(), 0);
}
@Test
@@ -351,7 +336,7 @@
longAppDuration, longAppDuration));
}
- private StartingWindowInfo createWindowInfo(int taskId, int themeResId) {
+ private StartingWindowInfo createWindowInfo(int taskId, int themeResId, IBinder appToken) {
StartingWindowInfo windowInfo = new StartingWindowInfo();
final ActivityInfo info = new ActivityInfo();
info.applicationInfo = new ApplicationInfo();
@@ -360,6 +345,7 @@
final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.topActivityInfo = info;
taskInfo.taskId = taskId;
+ windowInfo.appToken = appToken;
windowInfo.targetActivityInfo = info;
windowInfo.taskInfo = taskInfo;
windowInfo.topOpaqueWindowInsetsState = new InsetsState();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index e63bbeb..44f1f01 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -62,6 +62,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.util.ArraySet;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -99,6 +100,7 @@
import org.mockito.InOrder;
import java.util.ArrayList;
+import java.util.function.Function;
/**
* Tests for the shell transitions.
@@ -591,6 +593,68 @@
}
@Test
+ public void testInterleavedMerging() {
+ Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ Function<Boolean, IBinder> startATransition = (doMerge) -> {
+ IBinder token = new Binder();
+ if (doMerge) {
+ mDefaultHandler.setShouldMerge(token);
+ }
+ transitions.requestStartTransition(token,
+ new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+ TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ transitions.onTransitionReady(token, info, mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+ return token;
+ };
+
+ IBinder transitToken1 = startATransition.apply(false);
+ // merge first one
+ IBinder transitToken2 = startATransition.apply(true);
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, mDefaultHandler.mergeCount());
+
+ // don't merge next one
+ IBinder transitToken3 = startATransition.apply(false);
+ // make sure nothing happened (since it wasn't merged)
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, mDefaultHandler.mergeCount());
+
+ // make a mergable
+ IBinder transitToken4 = startATransition.apply(true);
+ // make sure nothing happened since there is a non-mergable pending.
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, mDefaultHandler.mergeCount());
+
+ // Queue up another mergable
+ IBinder transitToken5 = startATransition.apply(true);
+
+ // Queue up a non-mergable
+ IBinder transitToken6 = startATransition.apply(false);
+
+ // Our active now looks like: [playing, merged]
+ // and ready queue: [non-mergable, mergable, mergable, non-mergable]
+ // finish the playing one
+ mDefaultHandler.finishOne();
+ mMainExecutor.flushAll();
+ // Now we should have the non-mergable playing now with 2 merged:
+ // active: [playing, merged, merged] queue: [non-mergable]
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(2, mDefaultHandler.mergeCount());
+
+ mDefaultHandler.finishOne();
+ mMainExecutor.flushAll();
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(0, mDefaultHandler.mergeCount());
+
+ mDefaultHandler.finishOne();
+ mMainExecutor.flushAll();
+ }
+
+ @Test
public void testTransitionOrderMatchesCore() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -1016,6 +1080,7 @@
ArrayList<Transitions.TransitionFinishCallback> mFinishes = new ArrayList<>();
final ArrayList<IBinder> mMerged = new ArrayList<>();
boolean mSimulateMerge = false;
+ final ArraySet<IBinder> mShouldMerge = new ArraySet<>();
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@@ -1030,7 +1095,7 @@
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (!mSimulateMerge) return;
+ if (!(mSimulateMerge || mShouldMerge.contains(transition))) return;
mMerged.add(transition);
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
}
@@ -1046,12 +1111,23 @@
mSimulateMerge = sim;
}
+ void setShouldMerge(IBinder toMerge) {
+ mShouldMerge.add(toMerge);
+ }
+
void finishAll() {
final ArrayList<Transitions.TransitionFinishCallback> finishes = mFinishes;
mFinishes = new ArrayList<>();
for (int i = finishes.size() - 1; i >= 0; --i) {
finishes.get(i).onTransitionFinished(null /* wct */, null /* wctCB */);
}
+ mShouldMerge.clear();
+ }
+
+ void finishOne() {
+ Transitions.TransitionFinishCallback fin = mFinishes.remove(0);
+ mMerged.clear();
+ fin.onTransitionFinished(null /* wct */, null /* wctCB */);
}
int activeCount() {
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
index 5ecec4d..3125f08 100644
--- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
@@ -72,6 +72,7 @@
public static final int AMBIENT_LIGHT_MODE_LOW_LIGHT = 2;
private final DreamManager mDreamManager;
+ private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;
@Nullable
private final ComponentName mLowLightDreamComponent;
@@ -81,8 +82,10 @@
@Inject
public LowLightDreamManager(
DreamManager dreamManager,
+ LowLightTransitionCoordinator lowLightTransitionCoordinator,
@Named(LOW_LIGHT_DREAM_COMPONENT) @Nullable ComponentName lowLightDreamComponent) {
mDreamManager = dreamManager;
+ mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
mLowLightDreamComponent = lowLightDreamComponent;
}
@@ -111,7 +114,9 @@
mAmbientLightMode = ambientLightMode;
- mDreamManager.setSystemDreamComponent(mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT
- ? mLowLightDreamComponent : null);
+ boolean shouldEnterLowLight = mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT;
+ mLowLightTransitionCoordinator.notifyBeforeLowLightTransition(shouldEnterLowLight,
+ () -> mDreamManager.setSystemDreamComponent(
+ shouldEnterLowLight ? mLowLightDreamComponent : null));
}
}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java
new file mode 100644
index 0000000..874a2d5
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 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.dream.lowlight;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.annotation.Nullable;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Helper class that allows listening and running animations before entering or exiting low light.
+ */
+@Singleton
+public class LowLightTransitionCoordinator {
+ /**
+ * Listener that is notified before low light entry.
+ */
+ public interface LowLightEnterListener {
+ /**
+ * Callback that is notified before the device enters low light.
+ *
+ * @return an optional animator that will be waited upon before entering low light.
+ */
+ Animator onBeforeEnterLowLight();
+ }
+
+ /**
+ * Listener that is notified before low light exit.
+ */
+ public interface LowLightExitListener {
+ /**
+ * Callback that is notified before the device exits low light.
+ *
+ * @return an optional animator that will be waited upon before exiting low light.
+ */
+ Animator onBeforeExitLowLight();
+ }
+
+ private LowLightEnterListener mLowLightEnterListener;
+ private LowLightExitListener mLowLightExitListener;
+
+ @Inject
+ public LowLightTransitionCoordinator() {
+ }
+
+ /**
+ * Sets the listener for the low light enter event.
+ *
+ * Only one listener can be set at a time. This method will overwrite any previously set
+ * listener. Null can be used to unset the listener.
+ */
+ public void setLowLightEnterListener(@Nullable LowLightEnterListener lowLightEnterListener) {
+ mLowLightEnterListener = lowLightEnterListener;
+ }
+
+ /**
+ * Sets the listener for the low light exit event.
+ *
+ * Only one listener can be set at a time. This method will overwrite any previously set
+ * listener. Null can be used to unset the listener.
+ */
+ public void setLowLightExitListener(@Nullable LowLightExitListener lowLightExitListener) {
+ mLowLightExitListener = lowLightExitListener;
+ }
+
+ /**
+ * Notifies listeners that the device is about to enter or exit low light.
+ *
+ * @param entering true if listeners should be notified before entering low light, false if this
+ * is notifying before exiting.
+ * @param callback callback that will be run after listeners complete.
+ */
+ void notifyBeforeLowLightTransition(boolean entering, Runnable callback) {
+ Animator animator = null;
+
+ if (entering && mLowLightEnterListener != null) {
+ animator = mLowLightEnterListener.onBeforeEnterLowLight();
+ } else if (!entering && mLowLightExitListener != null) {
+ animator = mLowLightExitListener.onBeforeExitLowLight();
+ }
+
+ // If the listener returned an animator to indicate it was running an animation, run the
+ // callback after the animation completes, otherwise call the callback directly.
+ if (animator != null) {
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ callback.run();
+ }
+ });
+ } else {
+ callback.run();
+ }
+ }
+}
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
index 91a170f..4b95d8c 100644
--- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
@@ -21,7 +21,10 @@
import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_UNKNOWN;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -44,44 +47,52 @@
private DreamManager mDreamManager;
@Mock
+ private LowLightTransitionCoordinator mTransitionCoordinator;
+
+ @Mock
private ComponentName mDreamComponent;
+ LowLightDreamManager mLowLightDreamManager;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+
+ // Automatically run any provided Runnable to mTransitionCoordinator to simplify testing.
+ doAnswer(invocation -> {
+ ((Runnable) invocation.getArgument(1)).run();
+ return null;
+ }).when(mTransitionCoordinator).notifyBeforeLowLightTransition(anyBoolean(),
+ any(Runnable.class));
+
+ mLowLightDreamManager = new LowLightDreamManager(mDreamManager, mTransitionCoordinator,
+ mDreamComponent);
}
@Test
public void setAmbientLightMode_lowLight_setSystemDream() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mDreamComponent);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
-
+ verify(mTransitionCoordinator).notifyBeforeLowLightTransition(eq(true), any());
verify(mDreamManager).setSystemDreamComponent(mDreamComponent);
}
@Test
public void setAmbientLightMode_regularLight_clearSystemDream() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mDreamComponent);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR);
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR);
-
+ verify(mTransitionCoordinator).notifyBeforeLowLightTransition(eq(false), any());
verify(mDreamManager).setSystemDreamComponent(null);
}
@Test
public void setAmbientLightMode_defaultUnknownMode_clearSystemDream() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mDreamComponent);
-
// Set to low light first.
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
clearInvocations(mDreamManager);
// Return to default unknown mode.
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN);
verify(mDreamManager).setSystemDreamComponent(null);
}
@@ -89,7 +100,7 @@
@Test
public void setAmbientLightMode_dreamComponentNotSet_doNothing() {
final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- null /*dream component*/);
+ mTransitionCoordinator, null /*dream component*/);
lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java
new file mode 100644
index 0000000..81e1e33
--- /dev/null
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 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.dream.lowlight;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+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;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class LowLightTransitionCoordinatorTest {
+ @Mock
+ private LowLightTransitionCoordinator.LowLightEnterListener mEnterListener;
+
+ @Mock
+ private LowLightTransitionCoordinator.LowLightExitListener mExitListener;
+
+ @Mock
+ private Animator mAnimator;
+
+ @Captor
+ private ArgumentCaptor<Animator.AnimatorListener> mAnimatorListenerCaptor;
+
+ @Mock
+ private Runnable mRunnable;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void onEnterCalledOnListeners() {
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+
+ coordinator.setLowLightEnterListener(mEnterListener);
+
+ coordinator.notifyBeforeLowLightTransition(true, mRunnable);
+
+ verify(mEnterListener).onBeforeEnterLowLight();
+ verify(mRunnable).run();
+ }
+
+ @Test
+ public void onExitCalledOnListeners() {
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+
+ coordinator.setLowLightExitListener(mExitListener);
+
+ coordinator.notifyBeforeLowLightTransition(false, mRunnable);
+
+ verify(mExitListener).onBeforeExitLowLight();
+ verify(mRunnable).run();
+ }
+
+ @Test
+ public void listenerNotCalledAfterRemoval() {
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+
+ coordinator.setLowLightEnterListener(mEnterListener);
+ coordinator.setLowLightEnterListener(null);
+
+ coordinator.notifyBeforeLowLightTransition(true, mRunnable);
+
+ verifyZeroInteractions(mEnterListener);
+ verify(mRunnable).run();
+ }
+
+ @Test
+ public void runnableCalledAfterAnimationEnds() {
+ when(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator);
+
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+ coordinator.setLowLightEnterListener(mEnterListener);
+
+ coordinator.notifyBeforeLowLightTransition(true, mRunnable);
+
+ // Animator listener is added and the runnable is not run yet.
+ verify(mAnimator).addListener(mAnimatorListenerCaptor.capture());
+ verifyZeroInteractions(mRunnable);
+
+ // Runnable is run once the animation ends.
+ mAnimatorListenerCaptor.getValue().onAnimationEnd(null);
+ verify(mRunnable).run();
+ }
+}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 7228b89..3b12972 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -537,6 +537,7 @@
"AnimatorManager.cpp",
"CanvasTransform.cpp",
"DamageAccumulator.cpp",
+ "Gainmap.cpp",
"Interpolator.cpp",
"LightingInfo.cpp",
"Matrix.cpp",
diff --git a/libs/hwui/Gainmap.cpp b/libs/hwui/Gainmap.cpp
new file mode 100644
index 0000000..30f401e
--- /dev/null
+++ b/libs/hwui/Gainmap.cpp
@@ -0,0 +1,32 @@
+/**
+ * Copyright (C) 2023 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.
+ */
+#include "Gainmap.h"
+
+namespace android::uirenderer {
+
+sp<Gainmap> Gainmap::allocateHardwareGainmap(const sp<Gainmap>& srcGainmap) {
+ auto gainmap = sp<Gainmap>::make();
+ gainmap->info = srcGainmap->info;
+ const SkBitmap skSrcBitmap = srcGainmap->bitmap->getSkBitmap();
+ sk_sp<Bitmap> skBitmap(Bitmap::allocateHardwareBitmap(skSrcBitmap));
+ if (!skBitmap.get()) {
+ return nullptr;
+ }
+ gainmap->bitmap = std::move(skBitmap);
+ return gainmap;
+}
+
+} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/Gainmap.h b/libs/hwui/Gainmap.h
index 765f98a..3bc183a 100644
--- a/libs/hwui/Gainmap.h
+++ b/libs/hwui/Gainmap.h
@@ -27,6 +27,7 @@
public:
SkGainmapInfo info;
sk_sp<Bitmap> bitmap;
+ static sp<Gainmap> allocateHardwareGainmap(const sp<Gainmap>& srcGainmap);
};
} // namespace android::uirenderer
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 8266beb..9a06be0 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -498,7 +498,7 @@
return result;
}
-SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination) {
+SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination, bool isShared) {
ATRACE_CALL();
SkGainmapInfo gainmapInfo;
std::unique_ptr<SkStream> gainmapStream;
@@ -553,9 +553,12 @@
return SkCodec::kInternalError;
}
- // TODO: We don't currently parcel the gainmap, but if we should then also support
- // the shared allocator
- sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bm);
+ sk_sp<Bitmap> nativeBitmap;
+ if (isShared) {
+ nativeBitmap = Bitmap::allocateAshmemBitmap(&bm);
+ } else {
+ nativeBitmap = Bitmap::allocateHeapBitmap(&bm);
+ }
if (!nativeBitmap) {
ALOGE("OOM allocating Bitmap with dimensions %i x %i", bitmapInfo.width(),
bitmapInfo.height());
diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h
index 97573e1..b3781b5 100644
--- a/libs/hwui/hwui/ImageDecoder.h
+++ b/libs/hwui/hwui/ImageDecoder.h
@@ -79,7 +79,7 @@
// Set whether the ImageDecoder should handle RestorePrevious frames.
void setHandleRestorePrevious(bool handle);
- SkCodec::Result extractGainmap(Bitmap* destination);
+ SkCodec::Result extractGainmap(Bitmap* destination, bool isShared);
private:
// State machine for keeping track of how to handle RestorePrevious (RP)
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 3f9c4bd..6ee7576 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -386,15 +386,10 @@
return NULL;
}
if (hasGainmap) {
- auto gainmap = sp<uirenderer::Gainmap>::make();
- gainmap->info = original.gainmap()->info;
- const SkBitmap skSrcBitmap = original.gainmap()->bitmap->getSkBitmap();
- sk_sp<Bitmap> skBitmap(Bitmap::allocateHardwareBitmap(skSrcBitmap));
- if (!skBitmap.get()) {
- return NULL;
+ auto gm = uirenderer::Gainmap::allocateHardwareGainmap(original.gainmap());
+ if (gm) {
+ bitmap->setGainmap(std::move(gm));
}
- gainmap->bitmap = std::move(skBitmap);
- bitmap->setGainmap(std::move(gainmap));
}
return createBitmap(env, bitmap.release(), getPremulBitmapCreateFlags(isMutable));
}
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 571ab83..c57e6f0 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -637,7 +637,10 @@
return nullObjectReturn("Failed to allocate a hardware bitmap");
}
if (hasGainmap) {
- hardwareBitmap->setGainmap(std::move(gainmap));
+ auto gm = uirenderer::Gainmap::allocateHardwareGainmap(gainmap);
+ if (gm) {
+ hardwareBitmap->setGainmap(std::move(gm));
+ }
}
return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags,
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index f93be03..aeaa171 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -334,7 +334,10 @@
if (isHardware) {
sk_sp<Bitmap> hardwareBitmap = Bitmap::allocateHardwareBitmap(bitmap);
if (hasGainmap) {
- hardwareBitmap->setGainmap(std::move(gainmap));
+ auto gm = uirenderer::Gainmap::allocateHardwareGainmap(gainmap);
+ if (gm) {
+ hardwareBitmap->setGainmap(std::move(gm));
+ }
}
return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags);
}
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index fda7080..db1c188 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -354,7 +354,8 @@
// cost of RAM
if (result == SkCodec::kSuccess && !jpostProcess && !preferRamOverQuality) {
// The gainmap costs RAM to improve quality, so skip this if we're prioritizing RAM instead
- result = decoder->extractGainmap(nativeBitmap.get());
+ result = decoder->extractGainmap(nativeBitmap.get(),
+ allocator == kSharedMemory_Allocator ? true : false);
jexception = get_and_clear_exception(env);
}
@@ -469,8 +470,10 @@
if (hwBitmap) {
hwBitmap->setImmutable();
if (nativeBitmap->hasGainmap()) {
- // TODO: Also convert to a HW gainmap image
- hwBitmap->setGainmap(nativeBitmap->gainmap());
+ auto gm = uirenderer::Gainmap::allocateHardwareGainmap(nativeBitmap->gainmap());
+ if (gm) {
+ hwBitmap->setGainmap(std::move(gm));
+ }
}
return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags,
ninePatchChunk, ninePatchInsets);
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index f17129c..1af60b2 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -108,8 +108,9 @@
std::move(data), std::string_view(fontPath.c_str(), fontPath.size()),
fontPtr, fontSize, ttcIndex, builder->axes);
if (minikinFont == nullptr) {
- jniThrowException(env, "java/lang/IllegalArgumentException",
- "Failed to create internal object. maybe invalid font data.");
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "Failed to create internal object. maybe invalid font data. filePath %s",
+ fontPath.c_str());
return 0;
}
uint32_t localeListId = minikin::registerLocaleList(langTagStr.c_str());
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index f85bdee..5f5e214 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -252,10 +252,10 @@
if (o == null || getClass() != o.getClass()) return false;
final AudioMix that = (AudioMix) o;
- return (this.mRouteFlags == that.mRouteFlags)
- && (this.mRule == that.mRule)
- && (this.mMixType == that.mMixType)
- && (this.mFormat == that.mFormat);
+ return (mRouteFlags == that.mRouteFlags)
+ && (mMixType == that.mMixType)
+ && Objects.equals(mRule, that.mRule)
+ && Objects.equals(mFormat, that.mFormat);
}
/** @hide */
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index 440447e..ce97733 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -24,6 +24,7 @@
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Objects;
@@ -50,7 +51,8 @@
mMixes = conf.mMixes;
}
- AudioPolicyConfig(ArrayList<AudioMix> mixes) {
+ @VisibleForTesting
+ public AudioPolicyConfig(ArrayList<AudioMix> mixes) {
mMixes = mixes;
}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index d70e8b3..9d0662b 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -20,43 +20,73 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.hardware.display.VirtualDisplayConfig;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.Slog;
import android.view.ContentRecordingSession;
import android.view.Surface;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.Map;
+import java.util.Objects;
/**
* A token granting applications the ability to capture screen contents and/or
* record system audio. The exact capabilities granted depend on the type of
* MediaProjection.
*
- * <p>
- * A screen capture session can be started through {@link
+ * <p>A screen capture session can be started through {@link
* MediaProjectionManager#createScreenCaptureIntent}. This grants the ability to
* capture screen contents, but not system audio.
- * </p>
*/
public final class MediaProjection {
private static final String TAG = "MediaProjection";
+ /**
+ * Requires an app registers a {@link Callback} before invoking
+ * {@link #createVirtualDisplay(String, int, int, int, int, Surface, VirtualDisplay.Callback,
+ * Handler) createVirtualDisplay}.
+ *
+ * <p>Enabled after version 33 (Android T), so applies to target SDK of 34+ (Android U+).
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ static final long MEDIA_PROJECTION_REQUIRES_CALLBACK = 269849258L; // buganizer id
+
private final IMediaProjection mImpl;
private final Context mContext;
- private final Map<Callback, CallbackRecord> mCallbacks;
- @Nullable private IMediaProjectionManager mProjectionService = null;
+ private final DisplayManager mDisplayManager;
+ private final IMediaProjectionManager mProjectionService;
+ @NonNull
+ private final Map<Callback, CallbackRecord> mCallbacks = new ArrayMap<>();
/** @hide */
public MediaProjection(Context context, IMediaProjection impl) {
- mCallbacks = new ArrayMap<Callback, CallbackRecord>();
+ this(context, impl, IMediaProjectionManager.Stub.asInterface(
+ ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE)),
+ context.getSystemService(DisplayManager.class));
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public MediaProjection(Context context, IMediaProjection impl, IMediaProjectionManager service,
+ DisplayManager displayManager) {
mContext = context;
mImpl = impl;
try {
@@ -64,46 +94,44 @@
} catch (RemoteException e) {
throw new RuntimeException("Failed to start media projection", e);
}
+ mProjectionService = service;
+ mDisplayManager = displayManager;
}
/**
* Register a listener to receive notifications about when the {@link MediaProjection} or
* captured content changes state.
- * <p>
- * The callback should be registered before invoking
+ *
+ * <p>The callback must be registered before invoking
* {@link #createVirtualDisplay(String, int, int, int, int, Surface, VirtualDisplay.Callback,
- * Handler)}
- * to ensure that any notifications on the callback are not missed.
- * </p>
+ * Handler)} to ensure that any notifications on the callback are not missed. The client must
+ * implement {@link Callback#onStop()} and clean up any resources it is holding, e.g. the
+ * {@link VirtualDisplay} and {@link Surface}.
*
* @param callback The callback to call.
* @param handler The handler on which the callback should be invoked, or
* null if the callback should be invoked on the calling thread's looper.
- * @throws IllegalArgumentException If the given callback is null.
+ * @throws NullPointerException If the given callback is null.
* @see #unregisterCallback
*/
- public void registerCallback(Callback callback, Handler handler) {
- if (callback == null) {
- throw new IllegalArgumentException("callback should not be null");
- }
+ public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
+ final Callback c = Objects.requireNonNull(callback);
if (handler == null) {
handler = new Handler();
}
- mCallbacks.put(callback, new CallbackRecord(callback, handler));
+ mCallbacks.put(c, new CallbackRecord(c, handler));
}
/**
* Unregister a {@link MediaProjection} listener.
*
* @param callback The callback to unregister.
- * @throws IllegalArgumentException If the given callback is null.
+ * @throws NullPointerException If the given callback is null.
* @see #registerCallback
*/
- public void unregisterCallback(Callback callback) {
- if (callback == null) {
- throw new IllegalArgumentException("callback should not be null");
- }
- mCallbacks.remove(callback);
+ public void unregisterCallback(@NonNull Callback callback) {
+ final Callback c = Objects.requireNonNull(callback);
+ mCallbacks.remove(c);
}
/**
@@ -122,43 +150,55 @@
if (surface != null) {
builder.setSurface(surface);
}
- VirtualDisplay virtualDisplay = createVirtualDisplay(builder, callback, handler);
- return virtualDisplay;
+ return createVirtualDisplay(builder, callback, handler);
}
/**
* Creates a {@link android.hardware.display.VirtualDisplay} to capture the
* contents of the screen.
*
- * @param name The name of the virtual display, must be non-empty.
- * @param width The width of the virtual display in pixels. Must be
- * greater than 0.
- * @param height The height of the virtual display in pixels. Must be
- * greater than 0.
- * @param dpi The density of the virtual display in dpi. Must be greater
- * than 0.
- * @param surface The surface to which the content of the virtual display
- * should be rendered, or null if there is none initially.
- * @param flags A combination of virtual display flags. See {@link DisplayManager} for the full
- * list of flags.
- * @param callback Callback to call when the virtual display's state
- * changes, or null if none.
- * @param handler The {@link android.os.Handler} on which the callback should be
- * invoked, or null if the callback should be invoked on the calling
- * thread's main {@link android.os.Looper}.
+ * <p>To correctly clean up resources associated with a capture, the application must register a
+ * {@link Callback} before invocation. The app must override {@link Callback#onStop()} to clean
+ * up (by invoking{@link VirtualDisplay#release()}, {@link Surface#release()} and related
+ * resources).
*
- * @see android.hardware.display.VirtualDisplay
+ * @param name The name of the virtual display, must be non-empty.
+ * @param width The width of the virtual display in pixels. Must be greater than 0.
+ * @param height The height of the virtual display in pixels. Must be greater than 0.
+ * @param dpi The density of the virtual display in dpi. Must be greater than 0.
+ * @param surface The surface to which the content of the virtual display should be rendered,
+ * or null if there is none initially.
+ * @param flags A combination of virtual display flags. See {@link DisplayManager} for the
+ * full list of flags.
+ * @param callback Callback invoked when the virtual display's state changes, or null.
+ * @param handler The {@link android.os.Handler} on which the callback should be invoked, or
+ * null if the callback should be invoked on the calling thread's main
+ * {@link android.os.Looper}.
+ * @throws IllegalStateException If the target SDK is
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U} and
+ * up and no {@link Callback}
+ * is registered. If the target SDK is less than
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U}, no
+ * exception is thrown.
+ * @see VirtualDisplay
+ * @see VirtualDisplay.Callback
*/
public VirtualDisplay createVirtualDisplay(@NonNull String name,
int width, int height, int dpi, int flags, @Nullable Surface surface,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
+ if (shouldMediaProjectionRequireCallback()) {
+ if (mCallbacks.isEmpty()) {
+ throw new IllegalStateException(
+ "Must register a callback before starting capture, to manage resources in"
+ + " response to MediaProjection states.");
+ }
+ }
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
height, dpi).setFlags(flags);
if (surface != null) {
builder.setSurface(surface);
}
- VirtualDisplay virtualDisplay = createVirtualDisplay(builder, callback, handler);
- return virtualDisplay;
+ return createVirtualDisplay(builder, callback, handler);
}
/**
@@ -191,20 +231,21 @@
} else {
session = ContentRecordingSession.createTaskSession(launchCookie);
}
+ // Pass in the current session details, so they are guaranteed to only be set in
+ // WindowManagerService AFTER a VirtualDisplay is constructed (assuming there are no
+ // errors during set-up).
+ virtualDisplayConfig.setContentRecordingSession(session);
virtualDisplayConfig.setWindowManagerMirroringEnabled(true);
- final DisplayManager dm = mContext.getSystemService(DisplayManager.class);
- final VirtualDisplay virtualDisplay = dm.createVirtualDisplay(this,
+ final VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(this,
virtualDisplayConfig.build(), callback, handler, windowContext);
if (virtualDisplay == null) {
- // Since WM handling a new display and DM creating a new VirtualDisplay is async,
- // WM may have tried to start task recording and encountered an error that required
- // stopping recording entirely. The VirtualDisplay would then be null when the
- // MediaProjection is no longer active.
+ // Since WindowManager handling a new display and DisplayManager creating a new
+ // VirtualDisplay is async, WindowManager may have tried to start task recording
+ // and encountered an error that required stopping recording entirely. The
+ // VirtualDisplay would then be null and the MediaProjection is no longer active.
+ Slog.w(TAG, "Failed to create virtual display.");
return null;
}
- session.setDisplayId(virtualDisplay.getDisplay().getDisplayId());
- // Successfully set up, so save the current session details.
- getProjectionService().setContentRecordingSession(session, mImpl);
return virtualDisplay;
} catch (RemoteException e) {
// Can not capture if WMS is not accessible, so bail out.
@@ -212,12 +253,14 @@
}
}
- private IMediaProjectionManager getProjectionService() {
- if (mProjectionService == null) {
- mProjectionService = IMediaProjectionManager.Stub.asInterface(
- ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE));
- }
- return mProjectionService;
+ /**
+ * Returns {@code true} when MediaProjection requires the app registers a callback before
+ * beginning to capture via
+ * {@link #createVirtualDisplay(String, int, int, int, int, Surface, VirtualDisplay.Callback,
+ * Handler)}.
+ */
+ private boolean shouldMediaProjectionRequireCallback() {
+ return CompatChanges.isChangeEnabled(MEDIA_PROJECTION_REQUIRES_CALLBACK);
}
/**
@@ -245,28 +288,26 @@
public abstract static class Callback {
/**
* Called when the MediaProjection session is no longer valid.
- * <p>
- * Once a MediaProjection has been stopped, it's up to the application to release any
- * resources it may be holding (e.g. {@link android.hardware.display.VirtualDisplay}s).
- * </p>
+ *
+ * <p>Once a MediaProjection has been stopped, it's up to the application to release any
+ * resources it may be holding (e.g. releasing the {@link VirtualDisplay} and
+ * {@link Surface}).
*/
public void onStop() { }
/**
* Invoked immediately after capture begins or when the size of the captured region changes,
* providing the accurate sizing for the streamed capture.
- * <p>
- * The given width and height, in pixels, corresponds to the same width and height that
+ *
+ * <p>The given width and height, in pixels, corresponds to the same width and height that
* would be returned from {@link android.view.WindowMetrics#getBounds()} of the captured
* region.
- * </p>
- * <p>
- * If the recorded content has a different aspect ratio from either the
+ *
+ * <p>If the recorded content has a different aspect ratio from either the
* {@link VirtualDisplay} or output {@link Surface}, the captured stream has letterboxing
* (black bars) around the recorded content. The application can avoid the letterboxing
* around the recorded content by updating the size of both the {@link VirtualDisplay} and
* output {@link Surface}:
- * </p>
*
* <pre>
* @Override
@@ -291,13 +332,12 @@
/**
* Invoked immediately after capture begins or when the visibility of the captured region
* changes, providing the current visibility of the captured region.
- * <p>
- * Applications can take advantage of this callback by showing or hiding the captured
+ *
+ * <p>Applications can take advantage of this callback by showing or hiding the captured
* content from the output {@link Surface}, based on if the captured region is currently
* visible to the user.
- * </p>
- * <p>
- * For example, if the user elected to capture a single app (from the activity shown from
+ *
+ * <p>For example, if the user elected to capture a single app (from the activity shown from
* {@link MediaProjectionManager#createScreenCaptureIntent()}), the following scenarios
* trigger the callback:
* <ul>
@@ -314,7 +354,6 @@
* captured app, or the user navigates away from the captured app.
* </li>
* </ul>
- * </p>
*/
public void onCapturedContentVisibilityChanged(boolean isVisible) { }
}
@@ -342,7 +381,7 @@
}
}
- private final static class CallbackRecord {
+ private static final class CallbackRecord extends Callback {
private final Callback mCallback;
private final Handler mHandler;
@@ -351,6 +390,8 @@
mHandler = handler;
}
+
+ @Override
public void onStop() {
mHandler.post(new Runnable() {
@Override
@@ -360,10 +401,12 @@
});
}
+ @Override
public void onCapturedContentResize(int width, int height) {
mHandler.post(() -> mCallback.onCapturedContentResize(width, height));
}
+ @Override
public void onCapturedContentVisibilityChanged(boolean isVisible) {
mHandler.post(() -> mCallback.onCapturedContentVisibilityChanged(isVisible));
}
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index e9aa321..113c858 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -118,6 +118,9 @@
void requestAd(in IBinder sessionToken, in AdRequest request, int userId);
void notifyAdBuffer(in IBinder sessionToken, in AdBuffer buffer, int userId);
+ // For TV Message
+ void notifyTvMessage(in IBinder sessionToken, in String type, in Bundle data, int userId);
+
// For TV input hardware binding
List<TvInputHardwareInfo> getHardwareList();
ITvInputHardware acquireTvInputHardware(int deviceId, in ITvInputHardwareCallback callback,
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index 82875e5..165a9dd 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -76,4 +76,7 @@
// For ad request
void requestAd(in AdRequest request);
void notifyAdBuffer(in AdBuffer buffer);
+
+ // For TV messages
+ void notifyTvMessage(in String type, in Bundle data);
}
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 465b617..8389706 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -78,6 +78,7 @@
private static final int DO_SELECT_AUDIO_PRESENTATION = 29;
private static final int DO_TIME_SHIFT_SET_MODE = 30;
private static final int DO_SET_TV_MESSAGE_ENABLED = 31;
+ private static final int DO_NOTIFY_TV_MESSAGE = 32;
private final boolean mIsRecordingSession;
private final HandlerCaller mCaller;
@@ -277,6 +278,11 @@
mTvInputSessionImpl.notifyAdBuffer((AdBuffer) msg.obj);
break;
}
+ case DO_NOTIFY_TV_MESSAGE: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mTvInputSessionImpl.onTvMessageReceived((String) args.arg1, (Bundle) args.arg2);
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -463,6 +469,11 @@
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_AD_BUFFER, buffer));
}
+ @Override
+ public void notifyTvMessage(String type, Bundle data) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_NOTIFY_TV_MESSAGE, type, data));
+ }
+
private final class TvInputEventReceiver extends InputEventReceiver {
TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 8459538..55a753f 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -144,6 +144,13 @@
@StringDef({TV_MESSAGE_TYPE_WATERMARK, TV_MESSAGE_TYPE_CLOSED_CAPTION})
public @interface TvMessageType {}
+ /**
+ * This constant is used as a {@link Bundle} key for TV messages. The value of the key
+ * identifies the stream on the TV input source for which the watermark event is relevant to.
+ */
+ public static final String TV_MESSAGE_KEY_STREAM_ID =
+ "android.media.tv.TvInputManager.stream_id";
+
static final int VIDEO_UNAVAILABLE_REASON_START = 0;
static final int VIDEO_UNAVAILABLE_REASON_END = 18;
@@ -3221,6 +3228,17 @@
}
/**
+ * Sends TV messages to the service for testing purposes
+ */
+ public void notifyTvMessage(@NonNull @TvMessageType String type, @NonNull Bundle data) {
+ try {
+ mService.notifyTvMessage(mToken, type, data, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Starts TV program recording in the current recording session.
*
* @param programUri The URI for the TV program to record as a hint, built by
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 4bc137d..9f40d70 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1491,7 +1491,18 @@
* {@code false} otherwise.
*/
public void onSetTvMessageEnabled(@NonNull @TvInputManager.TvMessageType String type,
- boolean enabled){
+ boolean enabled) {
+ }
+
+ /**
+ * Called when a TV message is received
+ *
+ * @param type The type of message received, such as
+ * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
+ * @param data The raw data of the message
+ */
+ public void onTvMessage(@NonNull @TvInputManager.TvMessageType String type,
+ @NonNull Bundle data) {
}
/**
@@ -2043,6 +2054,10 @@
onAdBuffer(buffer);
}
+ void onTvMessageReceived(String type, Bundle data) {
+ onTvMessage(type, data);
+ }
+
/**
* Takes care of dispatching incoming input events and tells whether the event was handled.
*/
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 3ef61f2..5aeed1f 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.AttributionSource;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -640,6 +641,20 @@
}
}
+
+ /**
+ * Sends TV messages to the session for testing purposes
+ *
+ * @hide
+ */
+ @TestApi
+ public void notifyTvMessage(@TvInputManager.TvMessageType @NonNull String type,
+ @NonNull Bundle data) {
+ if (mSession != null) {
+ mSession.notifyTvMessage(type, data);
+ }
+ }
+
/**
* Sets the callback to be invoked when the time shift position is changed.
*
diff --git a/media/tests/AudioPolicyTest/Android.bp b/media/tests/AudioPolicyTest/Android.bp
index 63292ce..4624dfe 100644
--- a/media/tests/AudioPolicyTest/Android.bp
+++ b/media/tests/AudioPolicyTest/Android.bp
@@ -14,6 +14,7 @@
"androidx.test.ext.junit",
"androidx.test.rules",
"guava",
+ "guava-android-testlib",
"hamcrest-library",
"platform-test-annotations",
],
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java
new file mode 100644
index 0000000..bbca882
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2023 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.audiopolicytest;
+
+import static android.media.AudioFormat.CHANNEL_OUT_MONO;
+import static android.media.AudioFormat.CHANNEL_OUT_STEREO;
+import static android.media.AudioFormat.ENCODING_PCM_16BIT;
+import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_INJECTOR;
+import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_PLAYERS;
+import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID;
+import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_UID;
+
+import android.media.AudioFormat;
+import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
+import android.media.audiopolicy.AudioPolicyConfig;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.testing.EqualsTester;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for AudioMix.
+ *
+ * Run with "atest AudioMixUnitTests".
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AudioMixUnitTests {
+ private static final AudioFormat OUTPUT_FORMAT_STEREO_44KHZ_PCM =
+ new AudioFormat.Builder()
+ .setSampleRate(44000)
+ .setChannelMask(CHANNEL_OUT_STEREO)
+ .setEncoding(ENCODING_PCM_16BIT).build();
+ private static final AudioFormat OUTPUT_FORMAT_MONO_16KHZ_PCM =
+ new AudioFormat.Builder()
+ .setSampleRate(16000)
+ .setChannelMask(CHANNEL_OUT_MONO)
+ .setEncoding(ENCODING_PCM_16BIT).build();
+ private static final AudioFormat INPUT_FORMAT_MONO_16KHZ_PCM =
+ new AudioFormat.Builder()
+ .setSampleRate(16000)
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+ .setEncoding(ENCODING_PCM_16BIT).build();
+
+ @Test
+ public void testEquals() {
+ final EqualsTester equalsTester = new EqualsTester();
+
+ // --- Equality group 1
+ final AudioMix playbackAudioMixWithSessionId42AndUid123 =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_PLAYERS)
+ .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
+ .addMixRule(RULE_MATCH_UID, 123).build())
+ .setFormat(OUTPUT_FORMAT_STEREO_44KHZ_PCM)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+ final AudioMix playbackAudioMixWithUid123AndSessionId42 =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_PLAYERS)
+ .addMixRule(RULE_MATCH_UID, 123)
+ .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42).build())
+ .setFormat(OUTPUT_FORMAT_STEREO_44KHZ_PCM)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+ equalsTester.addEqualityGroup(
+ playbackAudioMixWithSessionId42AndUid123,
+ playbackAudioMixWithUid123AndSessionId42,
+ writeToAndFromParcel(playbackAudioMixWithSessionId42AndUid123),
+ writeToAndFromParcel(playbackAudioMixWithUid123AndSessionId42));
+
+ // --- Equality group 2
+ final AudioMix recordingAudioMixWithSessionId42AndUid123 =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_INJECTOR)
+ .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
+ .addMixRule(RULE_MATCH_UID, 123).build())
+ .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+ final AudioMix recordingAudioMixWithUid123AndSessionId42 =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_INJECTOR)
+ .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
+ .addMixRule(RULE_MATCH_UID, 123).build())
+ .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+ equalsTester.addEqualityGroup(recordingAudioMixWithSessionId42AndUid123,
+ recordingAudioMixWithUid123AndSessionId42,
+ writeToAndFromParcel(recordingAudioMixWithSessionId42AndUid123),
+ writeToAndFromParcel(recordingAudioMixWithUid123AndSessionId42));
+
+ // --- Equality group 3
+ final AudioMix recordingAudioMixWithSessionId42AndUid123Render =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_INJECTOR)
+ .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
+ .addMixRule(RULE_MATCH_UID, 123).build())
+ .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
+ .setRouteFlags(
+ AudioMix.ROUTE_FLAG_LOOP_BACK | AudioMix.ROUTE_FLAG_RENDER).build();
+ equalsTester.addEqualityGroup(recordingAudioMixWithSessionId42AndUid123Render,
+ writeToAndFromParcel(recordingAudioMixWithSessionId42AndUid123Render));
+
+ // --- Equality group 4
+ final AudioMix playbackAudioMixWithUid123 =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_PLAYERS)
+ .addMixRule(RULE_MATCH_UID, 123).build())
+ .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+ equalsTester.addEqualityGroup(playbackAudioMixWithUid123,
+ writeToAndFromParcel(playbackAudioMixWithUid123));
+
+ // --- Equality group 5
+ final AudioMix playbackAudioMixWithUid42 =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_PLAYERS)
+ .addMixRule(RULE_MATCH_UID, 42).build())
+ .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+ equalsTester.addEqualityGroup(playbackAudioMixWithUid42,
+ writeToAndFromParcel(playbackAudioMixWithUid42));
+
+ equalsTester.testEquals();
+ }
+
+ private static AudioMix writeToAndFromParcel(AudioMix audioMix) {
+ AudioPolicyConfig apc = new AudioPolicyConfig(new ArrayList<>(List.of(audioMix)));
+ Parcel parcel = Parcel.obtain();
+ apc.writeToParcel(parcel, /*flags=*/0);
+ parcel.setDataPosition(0);
+ AudioMix unmarshalledMix =
+ AudioPolicyConfig.CREATOR.createFromParcel(parcel).getMixes().get(0);
+ parcel.recycle();
+ return unmarshalledMix;
+ }
+}
diff --git a/media/tests/projection/Android.bp b/media/tests/projection/Android.bp
index 08d9501..e313c46 100644
--- a/media/tests/projection/Android.bp
+++ b/media/tests/projection/Android.bp
@@ -29,7 +29,9 @@
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
"testng",
+ "testables",
"truth-prebuilt",
+ "platform-compat-test-rules",
],
// Needed for mockito-target-extended-minus-junit4
diff --git a/media/tests/projection/AndroidManifest.xml b/media/tests/projection/AndroidManifest.xml
index 62f148c..0c97604 100644
--- a/media/tests/projection/AndroidManifest.xml
+++ b/media/tests/projection/AndroidManifest.xml
@@ -19,6 +19,7 @@
package="android.media.projection.mediaprojectiontests"
android:sharedUserId="com.android.uid.test">
<uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+ <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
<application android:debuggable="true"
android:testOnly="true">
diff --git a/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java b/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java
new file mode 100644
index 0000000..3cfc0fe
--- /dev/null
+++ b/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 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.media.projection;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * The connection between MediaProjection and system server is represented by IMediaProjection;
+ * outside the test it is implemented by the system server.
+ */
+public final class FakeIMediaProjection extends IMediaProjection.Stub {
+ boolean mIsStarted = false;
+ IBinder mLaunchCookie = null;
+ IMediaProjectionCallback mIMediaProjectionCallback = null;
+
+ @Override
+ public void start(IMediaProjectionCallback callback) throws RemoteException {
+ mIMediaProjectionCallback = callback;
+ mIsStarted = true;
+ }
+
+ @Override
+ public void stop() throws RemoteException {
+ // Pass along to the client's callback wrapper.
+ mIMediaProjectionCallback.onStop();
+ }
+
+ @Override
+ public boolean canProjectAudio() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean canProjectVideo() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean canProjectSecureVideo() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public int applyVirtualDisplayFlags(int flags) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public void registerCallback(IMediaProjectionCallback callback) throws RemoteException {
+
+ }
+
+ @Override
+ public void unregisterCallback(IMediaProjectionCallback callback) throws RemoteException {
+
+ }
+
+ @Override
+ public IBinder getLaunchCookie() throws RemoteException {
+ return mLaunchCookie;
+ }
+
+ @Override
+ public void setLaunchCookie(IBinder launchCookie) throws RemoteException {
+ mLaunchCookie = launchCookie;
+ }
+}
diff --git a/media/tests/projection/src/android/media/projection/MediaProjectionTest.java b/media/tests/projection/src/android/media/projection/MediaProjectionTest.java
new file mode 100644
index 0000000..bf616d7
--- /dev/null
+++ b/media/tests/projection/src/android/media/projection/MediaProjectionTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2023 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.media.projection;
+
+
+import static android.media.projection.MediaProjection.MEDIA_PROJECTION_REQUIRES_CALLBACK;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.annotation.Nullable;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.hardware.display.VirtualDisplayConfig;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+import android.view.Display;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+
+/**
+ * Tests for the {@link MediaProjection} class.
+ *
+ * Build/Install/Run:
+ * atest MediaProjectionTests:MediaProjectionTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionTest {
+ // Values for creating a VirtualDisplay.
+ private static final String VIRTUAL_DISPLAY_NAME = "MEDIA_PROJECTION_VIRTUAL_DISPLAY";
+ private static final int VIRTUAL_DISPLAY_WIDTH = 500;
+ private static final int VIRTUAL_DISPLAY_HEIGHT = 600;
+ private static final int VIRTUAL_DISPLAY_DENSITY = 100;
+ private static final int VIRTUAL_DISPLAY_FLAGS = 0;
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ // Fake the connection to the system server.
+ private final FakeIMediaProjection mFakeIMediaProjection = new FakeIMediaProjection();
+ // Callback registered by an app.
+ private MediaProjection mMediaProjection;
+
+ @Mock
+ private MediaProjection.Callback mMediaProjectionCallback;
+ @Mock
+ private IMediaProjectionManager mIMediaProjectionManager;
+ @Mock
+ private Display mDisplay;
+ @Mock
+ private VirtualDisplay.Callback mVirtualDisplayCallback;
+ @Mock
+ private VirtualDisplay mVirtualDisplay;
+ @Mock
+ private DisplayManager mDisplayManager;
+
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+ @Rule
+ public final TestableContext mTestableContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext());
+
+ private MockitoSession mMockingSession;
+
+ @Before
+ public void setup() throws Exception {
+ mMockingSession =
+ mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ doReturn(mock(IBinder.class)).when(mIMediaProjectionManager).asBinder();
+
+ // Support the MediaProjection instance.
+ mFakeIMediaProjection.setLaunchCookie(mock(IBinder.class));
+ mMediaProjection = new MediaProjection(mTestableContext, mFakeIMediaProjection,
+ mIMediaProjectionManager, mDisplayManager);
+
+ // Support creation of the VirtualDisplay.
+ mTestableContext.addMockSystemService(DisplayManager.class, mDisplayManager);
+ doReturn(mDisplay).when(mVirtualDisplay).getDisplay();
+ doReturn(DEFAULT_DISPLAY + 7).when(mDisplay).getDisplayId();
+ doReturn(mVirtualDisplay).when(mDisplayManager).createVirtualDisplay(
+ any(MediaProjection.class), any(VirtualDisplayConfig.class),
+ nullable(VirtualDisplay.Callback.class), nullable(Handler.class),
+ nullable(Context.class));
+ }
+
+ @After
+ public void tearDown() {
+ mMockingSession.finishMocking();
+ }
+
+ @Test
+ public void testConstruction() throws RemoteException {
+ assertThat(mMediaProjection).isNotNull();
+ assertThat(mFakeIMediaProjection.mIsStarted).isTrue();
+ }
+
+ @Test
+ public void testRegisterCallback_null() {
+ assertThrows(NullPointerException.class,
+ () -> mMediaProjection.registerCallback(null, mHandler));
+ }
+
+ @Test
+ public void testUnregisterCallback_null() {
+ mMediaProjection.registerCallback(mMediaProjectionCallback, mHandler);
+ assertThrows(NullPointerException.class,
+ () -> mMediaProjection.unregisterCallback(null));
+ }
+
+ @Test
+ @DisableCompatChanges({MEDIA_PROJECTION_REQUIRES_CALLBACK})
+ public void createVirtualDisplay_noCallbackRequired_missingMediaProjectionCallback() {
+ assertThat(createVirtualDisplay(null)).isNotNull();
+ assertThat(createVirtualDisplay(mVirtualDisplayCallback)).isNotNull();
+ }
+
+ @Test
+ @DisableCompatChanges({MEDIA_PROJECTION_REQUIRES_CALLBACK})
+ public void createVirtualDisplay_noCallbackRequired_givenMediaProjectionCallback() {
+ mMediaProjection.registerCallback(mMediaProjectionCallback, mHandler);
+ assertThat(createVirtualDisplay(null)).isNotNull();
+ assertThat(createVirtualDisplay(mVirtualDisplayCallback)).isNotNull();
+ }
+
+ @Test
+ @EnableCompatChanges({MEDIA_PROJECTION_REQUIRES_CALLBACK})
+ public void createVirtualDisplay_callbackRequired_missingMediaProjectionCallback() {
+ assertThrows(IllegalStateException.class,
+ () -> createVirtualDisplay(mVirtualDisplayCallback));
+ }
+
+ @Test
+ @EnableCompatChanges({MEDIA_PROJECTION_REQUIRES_CALLBACK})
+ public void createVirtualDisplay_callbackRequired_givenMediaProjectionCallback() {
+ mMediaProjection.registerCallback(mMediaProjectionCallback, mHandler);
+ assertThat(createVirtualDisplay(null)).isNotNull();
+ assertThat(createVirtualDisplay(mVirtualDisplayCallback)).isNotNull();
+ }
+
+ private VirtualDisplay createVirtualDisplay(@Nullable VirtualDisplay.Callback callback) {
+ // No recording will take place with a null surface.
+ return mMediaProjection.createVirtualDisplay(
+ VIRTUAL_DISPLAY_NAME, VIRTUAL_DISPLAY_WIDTH,
+ VIRTUAL_DISPLAY_HEIGHT, VIRTUAL_DISPLAY_DENSITY,
+ VIRTUAL_DISPLAY_FLAGS, /* surface = */ null,
+ callback, /* handler= */ mHandler);
+ }
+}
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index b842761..82e5a7f 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -36,17 +36,19 @@
<!-- Description of the privileges the application will get if associated with the companion device of WATCH profile for singleDevice(type) [CHAR LIMIT=NONE] -->
<string name="summary_watch_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, and access these permissions:</string>
- <!-- TODO(b/256140614) To replace all glasses related strings with final versions -->
<!-- ================= DEVICE_PROFILE_GLASSES ================= -->
+ <!-- Title of the device association confirmation dialog for glasses. -->
+ <string name="confirmation_title_glasses">Allow <strong><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g></strong> to manage <strong><xliff:g id="device_name" example="Glasses">%2$s</xliff:g></strong>?</string>
+
<!-- The name of the "glasses" device type [CHAR LIMIT=30] -->
<string name="profile_name_glasses">glasses</string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile (type) [CHAR LIMIT=NONE] -->
- <string name="summary_glasses">This app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
+ <string name="summary_glasses_multi_device">This app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile for singleDevice(type) [CHAR LIMIT=NONE] -->
- <string name="summary_glasses_single_device">The app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with these permissions:</string>
+ <string name="summary_glasses_single_device">This app will be allowed to access these permissions on your phone:</string>
<!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
@@ -81,17 +83,13 @@
<!-- Description of the helper dialog for COMPUTER profile. [CHAR LIMIT=NONE] -->
<string name="helper_summary_computer"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="Chromebook">%2$s</xliff:g> to access your phone\u2019s photos, media, and notifications</string>
- <!-- TODO(b/256140614) To replace all nearby_device_streaming related strings with final versions -->
<!-- ================= DEVICE_PROFILE_NEARBY_DEVICE_STREAMING ================= -->
<!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) [CHAR LIMIT=NONE] -->
- <string name="title_nearby_device_streaming">Allow <strong><xliff:g id="app_name" example="NearbyStreamer">%1$s</xliff:g></strong> to perform this action from your phone</string>
-
- <!-- Title of the helper dialog for NEARBY_DEVICE_STREAMING profile [CHAR LIMIT=30]. -->
- <string name="helper_title_nearby_device_streaming">Cross-device services</string>
+ <string name="title_nearby_device_streaming">Allow <strong><xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g></strong> to take this action?</string>
<!-- Description of the helper dialog for NEARBY_DEVICE_STREAMING profile. [CHAR LIMIT=NONE] -->
- <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="NearbyStreamerApp">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="NearbyDevice">%2$s</xliff:g> to stream content to nearby devices</string>
+ <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="NearbyStreamerApp">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_name" example="NearbyDevice">%2$s</xliff:g> to stream apps and other system features to nearby devices</string>
<!-- ================= null profile ================= -->
@@ -161,7 +159,7 @@
<string name="permission_app_streaming">Apps</string>
<!-- Nearby_device_streaming permission will be granted to the corresponding profile [CHAR LIMIT=45] -->
- <string name="permission_nearby_device_streaming">Nearby Device Streaming</string>
+ <string name="permission_nearby_device_streaming">Streaming</string>
<!-- Description of phone permission of corresponding profile [CHAR LIMIT=NONE] -->
<string name="permission_phone_summary">Can make and manage phone calls</string>
@@ -179,8 +177,7 @@
<string name="permission_calendar_summary">Can access your calendar</string>
<!-- Description of microphone permission of corresponding profile [CHAR LIMIT=NONE] -->
- <!-- TODO(b/256140614) Need the description for microphone permission -->
- <string name="permission_microphone_summary">Can record audio using the microphone</string>
+ <string name="permission_microphone_summary">Can record audio</string>
<!-- Description of nearby devices' permission of corresponding profile [CHAR LIMIT=NONE] -->
<string name="permission_nearby_devices_summary">Can find, connect to, and determine the relative position of nearby devices</string>
@@ -195,7 +192,6 @@
<string name="permission_storage_summary"></string>
<!-- Description of nearby_device_streaming permission of corresponding profile [CHAR LIMIT=NONE] -->
- <!-- TODO(b/256140614) Need the description for nearby devices' permission -->
- <string name="permission_nearby_device_streaming_summary">Stream content to a nearby device</string>
+ <string name="permission_nearby_device_streaming_summary">Stream apps and other system features from your phone</string>
</resources>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 918f9c6..8316f9d 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -30,6 +30,7 @@
import static com.android.companiondevicemanager.CompanionDeviceResources.MULTI_DEVICES_SUMMARIES;
import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_TYPES;
import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILES_NAME;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILES_NAME_MULTI;
import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_ICON;
import static com.android.companiondevicemanager.CompanionDeviceResources.SUMMARIES;
import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_PROFILES;
@@ -571,6 +572,7 @@
final String deviceProfile = mRequest.getDeviceProfile();
final String profileName;
+ final String profileNameMulti;
final Spanned summary;
final Drawable profileIcon;
final int summaryResourceId;
@@ -580,6 +582,7 @@
}
profileName = getString(PROFILES_NAME.get(deviceProfile));
+ profileNameMulti = getString(PROFILES_NAME_MULTI.get(deviceProfile));
profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));
summaryResourceId = MULTI_DEVICES_SUMMARIES.get(deviceProfile);
@@ -590,7 +593,7 @@
}
final Spanned title = getHtmlFromResources(
- this, R.string.chooser_title, profileName, appLabel);
+ this, R.string.chooser_title, profileNameMulti, appLabel);
mTitle.setText(title);
mSummary.setText(summary);
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
index e3fd354..7aed139 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
@@ -59,7 +59,7 @@
map.put(DEVICE_PROFILE_COMPUTER, R.string.title_computer);
map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, R.string.title_nearby_device_streaming);
map.put(DEVICE_PROFILE_WATCH, R.string.confirmation_title);
- map.put(DEVICE_PROFILE_GLASSES, R.string.confirmation_title);
+ map.put(DEVICE_PROFILE_GLASSES, R.string.confirmation_title_glasses);
map.put(null, R.string.confirmation_title);
TITLES = unmodifiableMap(map);
@@ -97,7 +97,7 @@
static {
final Map<String, Integer> map = new ArrayMap<>();
map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch);
- map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses);
+ map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses_multi_device);
map.put(null, R.string.summary_generic);
MULTI_DEVICES_SUMMARIES = unmodifiableMap(map);
@@ -113,6 +113,16 @@
PROFILES_NAME = unmodifiableMap(map);
}
+ static final Map<String, Integer> PROFILES_NAME_MULTI;
+ static {
+ final Map<String, Integer> map = new ArrayMap<>();
+ map.put(DEVICE_PROFILE_GLASSES, R.string.profile_name_generic);
+ map.put(DEVICE_PROFILE_WATCH, R.string.profile_name_watch);
+ map.put(null, R.string.profile_name_generic);
+
+ PROFILES_NAME_MULTI = unmodifiableMap(map);
+ }
+
static final Map<String, Integer> PROFILE_ICON;
static {
final Map<String, Integer> map = new ArrayMap<>();
@@ -133,7 +143,6 @@
SUPPORTED_PROFILES = unmodifiableSet(set);
}
-
static final Set<String> SUPPORTED_SELF_MANAGED_PROFILES;
static {
final Set<String> set = new ArraySet<>();
@@ -145,6 +154,4 @@
SUPPORTED_SELF_MANAGED_PROFILES = unmodifiableSet(set);
}
-
-
}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
index eae14a6..8f32dbb 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
@@ -21,6 +21,7 @@
import static android.companion.AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING;
import static com.android.companiondevicemanager.Utils.getApplicationIcon;
+import static com.android.companiondevicemanager.Utils.getApplicationLabel;
import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
import android.annotation.Nullable;
@@ -105,9 +106,11 @@
final String packageName = request.getPackageName();
final CharSequence displayName = request.getDisplayName();
final int userId = request.getUserId();
+ final CharSequence appLabel;
try {
applicationIcon = getApplicationIcon(getContext(), packageName);
+ appLabel = getApplicationLabel(getContext(), packageName, userId);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
mListener.onShowHelperDialogFailed();
@@ -119,7 +122,7 @@
mAppIcon = view.findViewById(R.id.app_icon);
mButton = view.findViewById(R.id.btn_back);
- final Spanned title;
+ final CharSequence title;
final Spanned summary;
switch (deviceProfile) {
@@ -136,8 +139,7 @@
break;
case DEVICE_PROFILE_NEARBY_DEVICE_STREAMING:
- title = getHtmlFromResources(getContext(),
- R.string.helper_title_nearby_device_streaming);
+ title = appLabel;
summary = getHtmlFromResources(
getContext(), R.string.helper_summary_nearby_device_streaming, title,
displayName);
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index ce18335..e49e3f1 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -22,6 +22,7 @@
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
import androidx.activity.result.IntentSenderRequest
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@@ -36,6 +37,9 @@
import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
+import com.android.credentialmanager.logging.LifecycleEvent
+import com.android.credentialmanager.logging.UIMetrics
+import com.android.internal.logging.UiEventLogger.UiEventEnum
/** One and only one of create or get state can be active at any given time. */
data class UiState(
@@ -56,6 +60,13 @@
var uiState by mutableStateOf(credManRepo.initState())
private set
+ var uiMetrics: UIMetrics = UIMetrics()
+
+ init{
+ uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_INIT,
+ credManRepo.requestInfo.appPackageName)
+ }
+
/**************************************************************************/
/***** Shared Callbacks *****/
/**************************************************************************/
@@ -76,6 +87,12 @@
fun onNewCredentialManagerRepo(credManRepo: CredentialManagerRepo) {
this.credManRepo = credManRepo
uiState = credManRepo.initState()
+
+ if (this.credManRepo.requestInfo.token != credManRepo.requestInfo.token) {
+ this.uiMetrics.resetInstanceId()
+ this.uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_NEW_REQUEST,
+ credManRepo.requestInfo.appPackageName)
+ }
}
fun launchProviderUi(
@@ -147,6 +164,8 @@
private fun onInternalError() {
Log.w(Constants.LOG_TAG, "UI closed due to illegal internal state")
+ this.uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_INTERNAL_ERROR,
+ credManRepo.requestInfo.appPackageName)
credManRepo.onParsingFailureCancel()
uiState = uiState.copy(dialogState = DialogState.COMPLETE)
}
@@ -374,4 +393,9 @@
onInternalError()
}
}
+
+ @Composable
+ fun logUiEvent(uiEventEnum: UiEventEnum) {
+ this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo.appPackageName)
+ }
}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index e61633f..b5c8989 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -60,8 +60,7 @@
import androidx.credentials.provider.CustomCredentialEntry
import androidx.credentials.provider.PasswordCredentialEntry
import androidx.credentials.provider.PublicKeyCredentialEntry
-import androidx.credentials.provider.RemoteCreateEntry
-import androidx.credentials.provider.RemoteCredentialEntry
+import androidx.credentials.provider.RemoteEntry
import org.json.JSONObject
// TODO: remove all !! checks
@@ -336,7 +335,7 @@
if (remoteEntry == null) {
return null
}
- val structuredRemoteEntry = RemoteCredentialEntry.fromSlice(remoteEntry.slice)
+ val structuredRemoteEntry = RemoteEntry.fromSlice(remoteEntry.slice)
?: return null
return RemoteEntryInfo(
providerId = providerId,
@@ -628,7 +627,7 @@
remoteEntry: Entry?,
): RemoteInfo? {
return if (remoteEntry != null) {
- val structuredRemoteEntry = RemoteCreateEntry.fromSlice(remoteEntry.slice)
+ val structuredRemoteEntry = RemoteEntry.fromSlice(remoteEntry.slice)
?: return null
RemoteInfo(
providerId = providerId,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
index 82d6952..26aadd9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
@@ -32,8 +32,7 @@
import androidx.credentials.provider.CreateEntry
import androidx.credentials.provider.PasswordCredentialEntry
import androidx.credentials.provider.PublicKeyCredentialEntry
-import androidx.credentials.provider.RemoteCreateEntry
-import androidx.credentials.provider.RemoteCredentialEntry
+import androidx.credentials.provider.RemoteEntry
import java.time.Instant
@@ -85,9 +84,7 @@
return Entry(
key,
subkey,
- RemoteCredentialEntry(pendingIntent, BeginGetPublicKeyCredentialOption(
- Bundle(), "id", "requestjson"
- )).slice
+ RemoteEntry(pendingIntent).slice
)
}
@@ -244,7 +241,7 @@
return Entry(
key,
subkey,
- RemoteCreateEntry(pendingIntent).slice
+ RemoteEntry(pendingIntent).slice
)
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 9fe7899..16827da 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -67,6 +67,8 @@
import com.android.credentialmanager.common.ui.PasskeyBenefitRow
import com.android.credentialmanager.common.ui.HeadlineText
import com.android.credentialmanager.common.ui.setBottomSheetSystemBarsColor
+import com.android.credentialmanager.logging.CreateCredentialEvent
+import com.android.internal.logging.UiEventLogger.UiEventEnum
@Composable
fun CreateCredentialScreen(
@@ -85,80 +87,89 @@
ProviderActivityState.NOT_APPLICABLE -> {
when (createCredentialUiState.currentScreenState) {
CreateScreenState.PASSKEY_INTRO -> PasskeyIntroCard(
- onConfirm = viewModel::createFlowOnConfirmIntro,
- onLearnMore = viewModel::createFlowOnLearnMore,
+ onConfirm = viewModel::createFlowOnConfirmIntro,
+ onLearnMore = viewModel::createFlowOnLearnMore,
+ onLog = { viewModel.logUiEvent(it) },
)
CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
- requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
- disabledProviderList = createCredentialUiState.disabledProviders,
- sortedCreateOptionsPairs =
- createCredentialUiState.sortedCreateOptionsPairs,
- hasRemoteEntry = createCredentialUiState.remoteEntry != null,
- onOptionSelected =
- viewModel::createFlowOnEntrySelectedFromFirstUseScreen,
- onDisabledProvidersSelected =
- viewModel::createFlowOnDisabledProvidersSelected,
- onMoreOptionsSelected =
- viewModel::createFlowOnMoreOptionsSelectedOnProviderSelection,
+ requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+ disabledProviderList = createCredentialUiState
+ .disabledProviders,
+ sortedCreateOptionsPairs =
+ createCredentialUiState.sortedCreateOptionsPairs,
+ hasRemoteEntry = createCredentialUiState.remoteEntry != null,
+ onOptionSelected =
+ viewModel::createFlowOnEntrySelectedFromFirstUseScreen,
+ onDisabledProvidersSelected =
+ viewModel::createFlowOnDisabledProvidersSelected,
+ onMoreOptionsSelected =
+ viewModel::createFlowOnMoreOptionsSelectedOnProviderSelection,
+ onLog = { viewModel.logUiEvent(it) },
)
CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
- requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
- enabledProviderList = createCredentialUiState.enabledProviders,
- providerInfo = createCredentialUiState.activeEntry?.activeProvider!!,
- hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
- createOptionInfo =
- createCredentialUiState.activeEntry.activeEntryInfo
- as CreateOptionInfo,
- onOptionSelected = viewModel::createFlowOnEntrySelected,
- onConfirm = viewModel::createFlowOnConfirmEntrySelected,
- onMoreOptionsSelected =
- viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
+ requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+ enabledProviderList = createCredentialUiState.enabledProviders,
+ providerInfo = createCredentialUiState
+ .activeEntry?.activeProvider!!,
+ hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
+ createOptionInfo =
+ createCredentialUiState.activeEntry.activeEntryInfo
+ as CreateOptionInfo,
+ onOptionSelected = viewModel::createFlowOnEntrySelected,
+ onConfirm = viewModel::createFlowOnConfirmEntrySelected,
+ onMoreOptionsSelected =
+ viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
+ onLog = { viewModel.logUiEvent(it) },
)
CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
- requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
- enabledProviderList = createCredentialUiState.enabledProviders,
- disabledProviderList = createCredentialUiState.disabledProviders,
- sortedCreateOptionsPairs =
- createCredentialUiState.sortedCreateOptionsPairs,
- hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
- isFromProviderSelection =
- createCredentialUiState.isFromProviderSelection!!,
- onBackProviderSelectionButtonSelected =
- viewModel::createFlowOnBackProviderSelectionButtonSelected,
- onBackCreationSelectionButtonSelected =
- viewModel::createFlowOnBackCreationSelectionButtonSelected,
- onOptionSelected =
- viewModel::createFlowOnEntrySelectedFromMoreOptionScreen,
- onDisabledProvidersSelected =
- viewModel::createFlowOnDisabledProvidersSelected,
- onRemoteEntrySelected = viewModel::createFlowOnEntrySelected,
+ requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+ enabledProviderList = createCredentialUiState.enabledProviders,
+ disabledProviderList = createCredentialUiState
+ .disabledProviders,
+ sortedCreateOptionsPairs =
+ createCredentialUiState.sortedCreateOptionsPairs,
+ hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
+ isFromProviderSelection =
+ createCredentialUiState.isFromProviderSelection!!,
+ onBackProviderSelectionButtonSelected =
+ viewModel::createFlowOnBackProviderSelectionButtonSelected,
+ onBackCreationSelectionButtonSelected =
+ viewModel::createFlowOnBackCreationSelectionButtonSelected,
+ onOptionSelected =
+ viewModel::createFlowOnEntrySelectedFromMoreOptionScreen,
+ onDisabledProvidersSelected =
+ viewModel::createFlowOnDisabledProvidersSelected,
+ onRemoteEntrySelected = viewModel::createFlowOnEntrySelected,
+ onLog = { viewModel.logUiEvent(it) },
)
CreateScreenState.MORE_OPTIONS_ROW_INTRO -> {
if (createCredentialUiState.activeEntry == null) {
viewModel.onIllegalUiState("Expect active entry to be non-null" +
- " upon default provider dialog.")
+ " upon default provider dialog.")
} else {
MoreOptionsRowIntroCard(
- selectedEntry = createCredentialUiState.activeEntry,
- onIllegalScreenState = viewModel::onIllegalUiState,
- onChangeDefaultSelected =
- viewModel::createFlowOnChangeDefaultSelected,
- onUseOnceSelected = viewModel::createFlowOnUseOnceSelected,
+ selectedEntry = createCredentialUiState.activeEntry,
+ onIllegalScreenState = viewModel::onIllegalUiState,
+ onChangeDefaultSelected =
+ viewModel::createFlowOnChangeDefaultSelected,
+ onUseOnceSelected = viewModel::createFlowOnUseOnceSelected,
+ onLog = { viewModel.logUiEvent(it) },
)
}
}
CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard(
- requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
- activeRemoteEntry =
- createCredentialUiState.activeEntry?.activeEntryInfo!!,
- onOptionSelected = viewModel::createFlowOnEntrySelected,
- onConfirm = viewModel::createFlowOnConfirmEntrySelected,
+ requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+ activeRemoteEntry =
+ createCredentialUiState.activeEntry?.activeEntryInfo!!,
+ onOptionSelected = viewModel::createFlowOnEntrySelected,
+ onConfirm = viewModel::createFlowOnConfirmEntrySelected,
+ onLog = { viewModel.logUiEvent(it) },
)
- CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO ->
- MoreAboutPasskeysIntroCard(
+ CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO -> MoreAboutPasskeysIntroCard(
onBackPasskeyIntroButtonSelected =
viewModel::createFlowOnBackPasskeyIntroButtonSelected,
- )
+ onLog = { viewModel.logUiEvent(it) },
+ )
}
}
ProviderActivityState.READY_TO_LAUNCH -> {
@@ -167,9 +178,14 @@
LaunchedEffect(viewModel.uiState.providerActivityState) {
viewModel.launchProviderUi(providerActivityLauncher)
}
+ viewModel.uiMetrics.log(
+ CreateCredentialEvent
+ .CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_READY_TO_LAUNCH)
}
ProviderActivityState.PENDING -> {
// Hide our content when the provider activity is active.
+ viewModel.uiMetrics.log(
+ CreateCredentialEvent.CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_PENDING)
}
}
},
@@ -181,6 +197,7 @@
fun PasskeyIntroCard(
onConfirm: () -> Unit,
onLearnMore: () -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard {
item {
@@ -245,6 +262,7 @@
)
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_PASSKEY_INTRO)
}
@Composable
@@ -256,6 +274,7 @@
onOptionSelected: (ActiveEntry) -> Unit,
onDisabledProvidersSelected: () -> Unit,
onMoreOptionsSelected: () -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard {
item { HeadlineIcon(bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()) }
@@ -319,21 +338,23 @@
}
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_PROVIDER_SELECTION)
}
@Composable
fun MoreOptionsSelectionCard(
- requestDisplayInfo: RequestDisplayInfo,
- enabledProviderList: List<EnabledProviderInfo>,
- disabledProviderList: List<DisabledProviderInfo>?,
- sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
- hasDefaultProvider: Boolean,
- isFromProviderSelection: Boolean,
- onBackProviderSelectionButtonSelected: () -> Unit,
- onBackCreationSelectionButtonSelected: () -> Unit,
- onOptionSelected: (ActiveEntry) -> Unit,
- onDisabledProvidersSelected: () -> Unit,
- onRemoteEntrySelected: (BaseEntry) -> Unit,
+ requestDisplayInfo: RequestDisplayInfo,
+ enabledProviderList: List<EnabledProviderInfo>,
+ disabledProviderList: List<DisabledProviderInfo>?,
+ sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
+ hasDefaultProvider: Boolean,
+ isFromProviderSelection: Boolean,
+ onBackProviderSelectionButtonSelected: () -> Unit,
+ onBackCreationSelectionButtonSelected: () -> Unit,
+ onOptionSelected: (ActiveEntry) -> Unit,
+ onDisabledProvidersSelected: () -> Unit,
+ onRemoteEntrySelected: (BaseEntry) -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard(topAppBar = {
MoreOptionTopAppBar(
@@ -394,14 +415,16 @@
}
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_MORE_OPTIONS_SELECTION)
}
@Composable
fun MoreOptionsRowIntroCard(
- selectedEntry: ActiveEntry,
- onIllegalScreenState: (String) -> Unit,
- onChangeDefaultSelected: () -> Unit,
- onUseOnceSelected: () -> Unit,
+ selectedEntry: ActiveEntry,
+ onIllegalScreenState: (String) -> Unit,
+ onChangeDefaultSelected: () -> Unit,
+ onUseOnceSelected: () -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
val entryInfo = selectedEntry.activeEntryInfo
if (entryInfo !is CreateOptionInfo) {
@@ -440,18 +463,20 @@
)
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_MORE_OPTIONS_ROW_INTRO)
}
@Composable
fun CreationSelectionCard(
- requestDisplayInfo: RequestDisplayInfo,
- enabledProviderList: List<EnabledProviderInfo>,
- providerInfo: EnabledProviderInfo,
- createOptionInfo: CreateOptionInfo,
- onOptionSelected: (BaseEntry) -> Unit,
- onConfirm: () -> Unit,
- onMoreOptionsSelected: () -> Unit,
- hasDefaultProvider: Boolean,
+ requestDisplayInfo: RequestDisplayInfo,
+ enabledProviderList: List<EnabledProviderInfo>,
+ providerInfo: EnabledProviderInfo,
+ createOptionInfo: CreateOptionInfo,
+ onOptionSelected: (BaseEntry) -> Unit,
+ onConfirm: () -> Unit,
+ onMoreOptionsSelected: () -> Unit,
+ hasDefaultProvider: Boolean,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard {
item {
@@ -537,14 +562,16 @@
item { BodySmallText(text = createOptionInfo.footerDescription) }
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_CREATION_OPTION_SELECTION)
}
@Composable
fun ExternalOnlySelectionCard(
- requestDisplayInfo: RequestDisplayInfo,
- activeRemoteEntry: BaseEntry,
- onOptionSelected: (BaseEntry) -> Unit,
- onConfirm: () -> Unit,
+ requestDisplayInfo: RequestDisplayInfo,
+ activeRemoteEntry: BaseEntry,
+ onOptionSelected: (BaseEntry) -> Unit,
+ onConfirm: () -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard {
item { HeadlineIcon(imageVector = Icons.Outlined.QrCodeScanner) }
@@ -572,11 +599,13 @@
)
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_EXTERNAL_ONLY_SELECTION)
}
@Composable
fun MoreAboutPasskeysIntroCard(
onBackPasskeyIntroButtonSelected: () -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard(
topAppBar = {
@@ -612,6 +641,7 @@
BodyMediumText(text = stringResource(R.string.seamless_transition_detail))
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_MORE_ABOUT_PASSKEYS_INTRO)
}
@Composable
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index a9f994d..96798a3 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -62,6 +62,8 @@
import com.android.credentialmanager.common.ui.Snackbar
import com.android.credentialmanager.common.ui.setTransparentSystemBarsColor
import com.android.credentialmanager.common.ui.setBottomSheetSystemBarsColor
+import com.android.credentialmanager.logging.GetCredentialEvent
+import com.android.internal.logging.UiEventLogger.UiEventEnum
@Composable
fun GetCredentialScreen(
@@ -75,7 +77,9 @@
RemoteCredentialSnackBarScreen(
onClick = viewModel::getFlowOnMoreOptionOnSnackBarSelected,
onCancel = viewModel::onUserCancel,
+ onLog = { viewModel.logUiEvent(it) },
)
+ viewModel.uiMetrics.log(GetCredentialEvent.CREDMAN_GET_CRED_SCREEN_REMOTE_ONLY)
} else if (getCredentialUiState.currentScreenState
== GetScreenState.UNLOCKED_AUTH_ENTRIES_ONLY) {
setTransparentSystemBarsColor(sysUiController)
@@ -84,7 +88,10 @@
getCredentialUiState.providerDisplayInfo.authenticationEntryList,
onCancel = viewModel::silentlyFinishActivity,
onLastLockedAuthEntryNotFound = viewModel::onLastLockedAuthEntryNotFoundError,
+ onLog = { viewModel.logUiEvent(it) },
)
+ viewModel.uiMetrics.log(GetCredentialEvent
+ .CREDMAN_GET_CRED_SCREEN_UNLOCKED_AUTH_ENTRIES_ONLY)
} else {
setBottomSheetSystemBarsColor(sysUiController)
ModalBottomSheet(
@@ -104,7 +111,10 @@
onEntrySelected = viewModel::getFlowOnEntrySelected,
onConfirm = viewModel::getFlowOnConfirmEntrySelected,
onMoreOptionSelected = viewModel::getFlowOnMoreOptionSelected,
+ onLog = { viewModel.logUiEvent(it) },
)
+ viewModel.uiMetrics.log(GetCredentialEvent
+ .CREDMAN_GET_CRED_SCREEN_PRIMARY_SELECTION)
} else {
AllSignInOptionCard(
providerInfoList = getCredentialUiState.providerInfoList,
@@ -114,7 +124,10 @@
viewModel::getFlowOnBackToPrimarySelectionScreen,
onCancel = viewModel::onUserCancel,
isNoAccount = getCredentialUiState.isNoAccount,
+ onLog = { viewModel.logUiEvent(it) },
)
+ viewModel.uiMetrics.log(GetCredentialEvent
+ .CREDMAN_GET_CRED_SCREEN_ALL_SIGN_IN_OPTIONS)
}
}
ProviderActivityState.READY_TO_LAUNCH -> {
@@ -123,9 +136,13 @@
LaunchedEffect(viewModel.uiState.providerActivityState) {
viewModel.launchProviderUi(providerActivityLauncher)
}
+ viewModel.uiMetrics.log(GetCredentialEvent
+ .CREDMAN_GET_CRED_PROVIDER_ACTIVITY_READY_TO_LAUNCH)
}
ProviderActivityState.PENDING -> {
// Hide our content when the provider activity is active.
+ viewModel.uiMetrics.log(GetCredentialEvent
+ .CREDMAN_GET_CRED_PROVIDER_ACTIVITY_PENDING)
}
}
},
@@ -144,6 +161,7 @@
onEntrySelected: (BaseEntry) -> Unit,
onConfirm: () -> Unit,
onMoreOptionSelected: () -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
val sortedUserNameToCredentialEntryList =
providerDisplayInfo.sortedUserNameToCredentialEntryList
@@ -248,6 +266,7 @@
)
}
}
+ onLog(GetCredentialEvent.CREDMAN_GET_CRED_PRIMARY_SELECTION_CARD)
}
/** Draws the secondary credential selection page, where all sign-in options are listed. */
@@ -259,6 +278,7 @@
onBackButtonClicked: () -> Unit,
onCancel: () -> Unit,
isNoAccount: Boolean,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
val sortedUserNameToCredentialEntryList =
providerDisplayInfo.sortedUserNameToCredentialEntryList
@@ -303,6 +323,7 @@
)
}
}
+ onLog(GetCredentialEvent.CREDMAN_GET_CRED_ALL_SIGN_IN_OPTION_CARD)
}
// TODO: create separate rows for primary and secondary pages.
@@ -466,6 +487,7 @@
fun RemoteCredentialSnackBarScreen(
onClick: (Boolean) -> Unit,
onCancel: () -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
Snackbar(
action = {
@@ -482,6 +504,7 @@
onDismiss = onCancel,
contentText = stringResource(R.string.get_dialog_use_saved_passkey_for),
)
+ onLog(GetCredentialEvent.CREDMAN_GET_CRED_REMOTE_CRED_SNACKBAR_SCREEN)
}
@Composable
@@ -489,6 +512,7 @@
authenticationEntryList: List<AuthenticationEntryInfo>,
onCancel: () -> Unit,
onLastLockedAuthEntryNotFound: () -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
val lastLocked = authenticationEntryList.firstOrNull({ it.isLastUnlocked })
if (lastLocked == null) {
@@ -500,4 +524,5 @@
onDismiss = onCancel,
contentText = stringResource(R.string.no_sign_in_info_in, lastLocked.providerDisplayName),
)
+ onLog(GetCredentialEvent.CREDMAN_GET_CRED_SCREEN_EMPTY_AUTH_SNACKBAR_SCREEN)
}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt
new file mode 100644
index 0000000..daa42be
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 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.credentialmanager.logging
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class CreateCredentialEvent(private val id: Int) : UiEventLogger.UiEventEnum {
+
+ @UiEvent(doc = "The create credential bottomsheet became visible on the screen.")
+ CREDMAN_CREATE_CRED_BOTTOMSHEET(1318),
+
+ @UiEvent(doc = "The provider activity is launched on the screen.")
+ CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_READY_TO_LAUNCH(1319),
+
+ @UiEvent(doc = "The provider activity is launched and we are waiting for its result. " +
+ "Contents Hidden.")
+ CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_PENDING(1320),
+
+ @UiEvent(doc = "The provider activity is not active or ready launched on the screen.")
+ CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_NOT_APPLICABLE(1321),
+
+ @UiEvent(doc = "The passkey introduction card is visible on screen.")
+ CREDMAN_CREATE_CRED_PASSKEY_INTRO(1322),
+
+ @UiEvent(doc = "The provider selection card is visible on screen.")
+ CREDMAN_CREATE_CRED_PROVIDER_SELECTION(1323),
+
+ @UiEvent(doc = "The creation option selection card is visible on screen.")
+ CREDMAN_CREATE_CRED_CREATION_OPTION_SELECTION(1324),
+
+ @UiEvent(doc = "The more option selection card is visible on screen.")
+ CREDMAN_CREATE_CRED_MORE_OPTIONS_SELECTION(1325),
+
+ @UiEvent(doc = "The more options row intro card is visible on screen.")
+ CREDMAN_CREATE_CRED_MORE_OPTIONS_ROW_INTRO(1326),
+
+ @UiEvent(doc = "The external only selection card is visible on screen.")
+ CREDMAN_CREATE_CRED_EXTERNAL_ONLY_SELECTION(1327),
+
+ @UiEvent(doc = "The more about passkeys intro card is visible on screen.")
+ CREDMAN_CREATE_CRED_MORE_ABOUT_PASSKEYS_INTRO(1328);
+
+ override fun getId(): Int {
+ return this.id
+ }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt
new file mode 100644
index 0000000..8de8895
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 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.credentialmanager.logging
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class GetCredentialEvent(private val id: Int) : UiEventLogger.UiEventEnum {
+
+ @UiEvent(doc = "The The snackbar only page when there's no account but only a remoteEntry " +
+ "visible on the screen.")
+ CREDMAN_GET_CRED_SCREEN_REMOTE_ONLY(1332),
+
+ @UiEvent(doc = "The snackbar when there are only auth entries and all of them are empty.")
+ CREDMAN_GET_CRED_SCREEN_UNLOCKED_AUTH_ENTRIES_ONLY(1333),
+
+ @UiEvent(doc = "The primary credential selection page is displayed on screen.")
+ CREDMAN_GET_CRED_SCREEN_PRIMARY_SELECTION(1334),
+
+ @UiEvent(doc = "The secondary credential selection page, where all sign-in options are " +
+ "listed is displayed on the screen.")
+ CREDMAN_GET_CRED_SCREEN_ALL_SIGN_IN_OPTIONS(1335),
+
+ @UiEvent(doc = "The provider activity is not active nor is any ready for launch on the screen.")
+ CREDMAN_GET_CRED_PROVIDER_ACTIVITY_NOT_APPLICABLE(1336),
+
+ @UiEvent(doc = "The provider activity is ready to be launched on the screen.")
+ CREDMAN_GET_CRED_PROVIDER_ACTIVITY_READY_TO_LAUNCH(1337),
+
+ @UiEvent(doc = "The provider activity is launched and we are waiting for its result. " +
+ "Contents Hidden.")
+ CREDMAN_GET_CRED_PROVIDER_ACTIVITY_PENDING(1338),
+
+ @UiEvent(doc = "The remote credential snackbar screen is visible.")
+ CREDMAN_GET_CRED_REMOTE_CRED_SNACKBAR_SCREEN(1339),
+
+ @UiEvent(doc = "The empty auth snackbar screen is visible.")
+ CREDMAN_GET_CRED_SCREEN_EMPTY_AUTH_SNACKBAR_SCREEN(1340),
+
+ @UiEvent(doc = "The primary selection card is visible on screen.")
+ CREDMAN_GET_CRED_PRIMARY_SELECTION_CARD(1341),
+
+ @UiEvent(doc = "The all sign in option card is visible on screen.")
+ CREDMAN_GET_CRED_ALL_SIGN_IN_OPTION_CARD(1342);
+
+ override fun getId(): Int {
+ return this.id
+ }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/LifecycleEvent.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/LifecycleEvent.kt
new file mode 100644
index 0000000..1ede64d
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/LifecycleEvent.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 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.credentialmanager.logging
+
+import com.android.internal.logging.UiEventLogger.UiEventEnum.RESERVE_NEW_UI_EVENT_ID
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class LifecycleEvent(private val id: Int) : UiEventLogger.UiEventEnum {
+
+ @UiEvent(doc = "A new activity is initialized for processing the request.")
+ CREDMAN_ACTIVITY_INIT(1343),
+
+ @UiEvent(doc = "An existing activity received a new request to process.")
+ CREDMAN_ACTIVITY_NEW_REQUEST(1344),
+
+ @UiEvent(doc = "The UI closed due to illegal internal state.")
+ CREDMAN_ACTIVITY_INTERNAL_ERROR(1345);
+
+ override fun getId(): Int {
+ return this.id
+ }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/UIMetrics.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/UIMetrics.kt
new file mode 100644
index 0000000..035c1e4
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/UIMetrics.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 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.credentialmanager.logging
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.UiEventLoggerImpl
+
+class UIMetrics() {
+ private val INSTANCE_ID_MAX = 1 shl 20
+ private val mUiEventLogger: UiEventLogger = UiEventLoggerImpl()
+ val mInstanceIdSequence: InstanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX)
+
+ var mInstanceId: InstanceId = mInstanceIdSequence.newInstanceId()
+
+ fun resetInstanceId() {
+ this.mInstanceId = mInstanceIdSequence.newInstanceId()
+ }
+
+ @Composable
+ fun log(event: UiEventLogger.UiEventEnum) {
+ val instanceId: InstanceId = mInstanceId
+ LaunchedEffect(true) {
+ mUiEventLogger.log(event, instanceId)
+ }
+ }
+
+ @Composable
+ fun log(event: UiEventLogger.UiEventEnum, packageName: String) {
+ val instanceId: InstanceId = mInstanceId
+ LaunchedEffect(true) {
+ mUiEventLogger.logWithInstanceId(event, /*uid=*/0, packageName, instanceId)
+ }
+ }
+
+ @Composable
+ fun log(event: UiEventLogger.UiEventEnum, instanceId: InstanceId, packageName: String) {
+ LaunchedEffect(true) {
+ mUiEventLogger.logWithInstanceId(event, /*uid=*/0, packageName, instanceId)
+ }
+ }
+
+ fun logNormal(event: UiEventLogger.UiEventEnum, packageName: String) {
+ mUiEventLogger.logWithInstanceId(event, /*uid=*/0, packageName, mInstanceId)
+ }
+}
diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
index 4ed7e19..10b004e 100644
--- a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
+++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
@@ -29,6 +29,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.SparseIntArray;
@@ -36,6 +37,7 @@
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -57,6 +59,7 @@
private final SecureSettings mSecureSettings;
private String[] mDeviceStateRotationLockDefaults;
private SparseIntArray mDeviceStateRotationLockSettings;
+ private SparseIntArray mDeviceStateDefaultRotationLockSettings;
private SparseIntArray mDeviceStateRotationLockFallbackSettings;
private String mLastSettingValue;
private List<SettableDeviceState> mSettableDeviceStates;
@@ -93,9 +96,7 @@
/** Returns true if device-state based rotation lock settings are enabled. */
public static boolean isDeviceStateRotationLockEnabled(Context context) {
return context.getResources()
- .getStringArray(R.array.config_perDeviceStateRotationLockDefaults)
- .length
- > 0;
+ .getStringArray(R.array.config_perDeviceStateRotationLockDefaults).length > 0;
}
private void listenForSettingsChange() {
@@ -228,6 +229,15 @@
try {
key = Integer.parseInt(values[i++]);
value = Integer.parseInt(values[i++]);
+ boolean isPersistedValueIgnored = value == DEVICE_STATE_ROTATION_LOCK_IGNORED;
+ boolean isDefaultValueIgnored = mDeviceStateDefaultRotationLockSettings.get(key)
+ == DEVICE_STATE_ROTATION_LOCK_IGNORED;
+ if (isPersistedValueIgnored != isDefaultValueIgnored) {
+ Log.w(TAG, "Conflict for ignored device state " + key
+ + ". Falling back on defaults");
+ fallbackOnDefaults();
+ return;
+ }
mDeviceStateRotationLockSettings.put(key, value);
} catch (NumberFormatException e) {
Log.wtf(TAG, "Error deserializing one of the saved settings", e);
@@ -276,6 +286,9 @@
}
private void persistSettingIfChanged(String newSettingValue) {
+ Log.v(TAG, "persistSettingIfChanged: "
+ + "last=" + mLastSettingValue + ", "
+ + "new=" + newSettingValue);
if (TextUtils.equals(mLastSettingValue, newSettingValue)) {
return;
}
@@ -288,6 +301,8 @@
private void loadDefaults() {
mSettableDeviceStates = new ArrayList<>(mDeviceStateRotationLockDefaults.length);
+ mDeviceStateDefaultRotationLockSettings = new SparseIntArray(
+ mDeviceStateRotationLockDefaults.length);
mDeviceStateRotationLockSettings = new SparseIntArray(
mDeviceStateRotationLockDefaults.length);
mDeviceStateRotationLockFallbackSettings = new SparseIntArray(1);
@@ -311,6 +326,7 @@
boolean isSettable = rotationLockSetting != DEVICE_STATE_ROTATION_LOCK_IGNORED;
mSettableDeviceStates.add(new SettableDeviceState(deviceState, isSettable));
mDeviceStateRotationLockSettings.put(deviceState, rotationLockSetting);
+ mDeviceStateDefaultRotationLockSettings.put(deviceState, rotationLockSetting);
} catch (NumberFormatException e) {
Log.wtf(TAG, "Error parsing settings entry. Entry was: " + entry, e);
return;
@@ -318,6 +334,22 @@
}
}
+ /** Dumps internal state. */
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("DeviceStateRotationLockSettingsManager");
+ pw.increaseIndent();
+ pw.println("mDeviceStateRotationLockDefaults: " + Arrays.toString(
+ mDeviceStateRotationLockDefaults));
+ pw.println("mDeviceStateDefaultRotationLockSettings: "
+ + mDeviceStateDefaultRotationLockSettings);
+ pw.println("mDeviceStateRotationLockSettings: " + mDeviceStateRotationLockSettings);
+ pw.println("mDeviceStateRotationLockFallbackSettings: "
+ + mDeviceStateRotationLockFallbackSettings);
+ pw.println("mSettableDeviceStates: " + mSettableDeviceStates);
+ pw.println("mLastSettingValue: " + mLastSettingValue);
+ pw.decreaseIndent();
+ }
+
/**
* Called when the persisted settings have changed, requiring a reinitialization of the
* in-memory map.
@@ -372,5 +404,13 @@
public int hashCode() {
return Objects.hash(mDeviceState, mIsSettable);
}
+
+ @Override
+ public String toString() {
+ return "SettableDeviceState{"
+ + "mDeviceState=" + mDeviceState
+ + ", mIsSettable=" + mIsSettable
+ + '}';
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
index 78df0f2..ca88f8d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.spa.framework.common
+import android.app.settings.SettingsEnums
import android.os.Bundle
// Defines the category of the log, for quick filter
@@ -31,20 +32,21 @@
}
// Defines the log events in Spa.
-enum class LogEvent {
+enum class LogEvent(val action: Int) {
// Page related events.
- PAGE_ENTER,
- PAGE_LEAVE,
+ PAGE_ENTER(SettingsEnums.PAGE_VISIBLE),
+ PAGE_LEAVE(SettingsEnums.PAGE_HIDE),
// Entry related events.
- ENTRY_CLICK,
- ENTRY_SWITCH,
+ ENTRY_CLICK(SettingsEnums.ACTION_SETTINGS_TILE_CLICK),
+ ENTRY_SWITCH(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
}
internal const val LOG_DATA_DISPLAY_NAME = "name"
-internal const val LOG_DATA_SESSION_NAME = "session"
internal const val LOG_DATA_SWITCH_STATUS = "switch"
+const val LOG_DATA_SESSION_NAME = "session"
+
/**
* The interface of logger in Spa
*/
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
index 2c3c2e0..d8c35a3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
@@ -22,9 +22,11 @@
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+const val SESSION_UNKNOWN = "unknown"
const val SESSION_BROWSE = "browse"
const val SESSION_SEARCH = "search"
const val SESSION_SLICE = "slice"
+const val SESSION_EXTERNAL = "external"
const val KEY_DESTINATION = "spaActivityDestination"
const val KEY_HIGHLIGHT_ENTRY = "highlightEntry"
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index b92b3d6..ff80f52 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -868,6 +868,11 @@
<!-- UI debug setting: force right to left layout summary [CHAR LIMIT=100] -->
<string name="force_rtl_layout_all_locales_summary">Force screen layout direction to RTL for all locales</string>
+ <!-- UI debug setting: make navigation bar background color transparent by default [CHAR LIMIT=50] -->
+ <string name="transparent_navigation_bar">Transparent navigation bar</string>
+ <!-- UI debug setting: make navigation bar background color transparent by default summary [CHAR LIMIT=100] -->
+ <string name="transparent_navigation_bar_summary">Make navigation bar background color transparent by default</string>
+
<!-- UI debug setting: enable or disable window blurs [CHAR LIMIT=50] -->
<string name="window_blurs">Allow window-level blurs</string>
@@ -1648,4 +1653,7 @@
<item>Move right</item>
<item>Move up</item>
</string-array>
+
+ <!-- Formatting states for the scale of font size, in percent. Double "%" is required to represent the symbol "%". [CHAR LIMIT=20] -->
+ <string name="font_scale_percentage"> <xliff:g id="percentage">%1$d</xliff:g> %%</string>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
index fb06976..3ec5eba 100644
--- a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
@@ -23,6 +23,7 @@
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
@@ -132,6 +133,11 @@
}
}
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ public boolean isSwitchEnabled() {
+ return mEnableSwitch;
+ }
+
/**
* If admin is not null, disables the switch.
* Otherwise, keep it enabled.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 63bb2fe..a3d632c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -210,7 +210,7 @@
synchronized (mProfileLock) {
if (profile instanceof A2dpProfile || profile instanceof HeadsetProfile
- || profile instanceof HearingAidProfile) {
+ || profile instanceof HearingAidProfile || profile instanceof LeAudioProfile) {
setProfileConnectedStatus(profile.getProfileId(), false);
switch (newProfileState) {
case BluetoothProfile.STATE_CONNECTED:
@@ -228,7 +228,20 @@
case BluetoothProfile.STATE_DISCONNECTED:
if (mHandler.hasMessages(profile.getProfileId())) {
mHandler.removeMessages(profile.getProfileId());
- setProfileConnectedStatus(profile.getProfileId(), true);
+ if (profile.getConnectionPolicy(mDevice) >
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+ /*
+ * If we received state DISCONNECTED and previous state was
+ * CONNECTING and connection policy is FORBIDDEN or UNKNOWN
+ * then it's not really a failure to connect.
+ *
+ * Connection profile is considered as failed when connection
+ * policy indicates that profile should be connected
+ * but it got disconnected.
+ */
+ Log.w(TAG, "onProfileStateChanged(): Failed to connect profile");
+ setProfileConnectedStatus(profile.getProfileId(), true);
+ }
}
break;
default:
@@ -1205,6 +1218,13 @@
}
private boolean isProfileConnectedFail() {
+ Log.d(TAG, "anonymizedAddress=" + mDevice.getAnonymizedAddress()
+ + " mIsA2dpProfileConnectedFail=" + mIsA2dpProfileConnectedFail
+ + " mIsHearingAidProfileConnectedFail=" + mIsHearingAidProfileConnectedFail
+ + " mIsLeAudioProfileConnectedFail=" + mIsLeAudioProfileConnectedFail
+ + " mIsHeadsetProfileConnectedFail=" + mIsHeadsetProfileConnectedFail
+ + " isConnectedSapDevice()=" + isConnectedSapDevice());
+
return mIsA2dpProfileConnectedFail || mIsHearingAidProfileConnectedFail
|| (!isConnectedSapDevice() && mIsHeadsetProfileConnectedFail)
|| mIsLeAudioProfileConnectedFail;
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
index 81006dd..0fa15eb 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
@@ -33,7 +33,10 @@
import com.android.internal.R;
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager.SettableDeviceState;
+import com.google.common.truth.Expect;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -45,6 +48,8 @@
@RunWith(AndroidJUnit4.class)
public class DeviceStateRotationLockSettingsManagerTest {
+ @Rule public Expect mExpect = Expect.create();
+
@Mock private Context mMockContext;
@Mock private Resources mMockResources;
@@ -117,4 +122,40 @@
new SettableDeviceState(/* deviceState= */ 0, /* isSettable= */ false)
).inOrder();
}
+
+ @Test
+ public void persistedInvalidIgnoredState_returnsDefaults() {
+ when(mMockResources.getStringArray(
+ R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
+ new String[]{"0:1", "1:0:2", "2:2"});
+ // Here 2 has IGNORED, and in the defaults 1 has IGNORED.
+ persistSettings("0:2:2:0:1:2");
+ DeviceStateRotationLockSettingsManager manager =
+ new DeviceStateRotationLockSettingsManager(mMockContext, mFakeSecureSettings);
+
+ mExpect.that(manager.getRotationLockSetting(0)).isEqualTo(1);
+ mExpect.that(manager.getRotationLockSetting(1)).isEqualTo(2);
+ mExpect.that(manager.getRotationLockSetting(2)).isEqualTo(2);
+ }
+
+ @Test
+ public void persistedValidValues_returnsPersistedValues() {
+ when(mMockResources.getStringArray(
+ R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
+ new String[]{"0:1", "1:0:2", "2:2"});
+ persistSettings("0:2:1:0:2:1");
+ DeviceStateRotationLockSettingsManager manager =
+ new DeviceStateRotationLockSettingsManager(mMockContext, mFakeSecureSettings);
+
+ mExpect.that(manager.getRotationLockSetting(0)).isEqualTo(2);
+ mExpect.that(manager.getRotationLockSetting(1)).isEqualTo(1);
+ mExpect.that(manager.getRotationLockSetting(2)).isEqualTo(1);
+ }
+
+ private void persistSettings(String value) {
+ mFakeSecureSettings.putStringForUser(
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ value,
+ UserHandle.USER_CURRENT);
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 77c19a1..1c179f8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -97,15 +97,76 @@
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
when(mDevice.getAddress()).thenReturn(DEVICE_ADDRESS);
when(mHfpProfile.isProfileReady()).thenReturn(true);
+ when(mHfpProfile.getProfileId()).thenReturn(BluetoothProfile.HEADSET);
when(mA2dpProfile.isProfileReady()).thenReturn(true);
+ when(mA2dpProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP);
when(mPanProfile.isProfileReady()).thenReturn(true);
+ when(mPanProfile.getProfileId()).thenReturn(BluetoothProfile.PAN);
when(mHearingAidProfile.isProfileReady()).thenReturn(true);
+ when(mHearingAidProfile.getProfileId()).thenReturn(BluetoothProfile.HEARING_AID);
+ when(mLeAudioProfile.isProfileReady()).thenReturn(true);
+ when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO);
mCachedDevice = spy(new CachedBluetoothDevice(mContext, mProfileManager, mDevice));
mSubCachedDevice = spy(new CachedBluetoothDevice(mContext, mProfileManager, mSubDevice));
doAnswer((invocation) -> mBatteryLevel).when(mCachedDevice).getBatteryLevel();
doAnswer((invocation) -> mBatteryLevel).when(mSubCachedDevice).getBatteryLevel();
}
+ private void testTransitionFromConnectingToDisconnected(
+ LocalBluetoothProfile connectingProfile, LocalBluetoothProfile connectedProfile,
+ int connectionPolicy, String expectedSummary) {
+ // Arrange:
+ // At least one profile has to be connected
+ updateProfileStatus(connectedProfile, BluetoothProfile.STATE_CONNECTED);
+ // Set profile under test to CONNECTING
+ updateProfileStatus(connectingProfile, BluetoothProfile.STATE_CONNECTING);
+ // Set connection policy
+ when(connectingProfile.getConnectionPolicy(mDevice)).thenReturn(connectionPolicy);
+
+ // Act & Assert:
+ // Get the expected connection summary.
+ updateProfileStatus(connectingProfile, BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(expectedSummary);
+ }
+
+ @Test
+ public void onProfileStateChanged_testConnectingToDisconnected_policyAllowed_problem() {
+ String connectTimeoutString = mContext.getString(R.string.profile_connect_timeout_subtext);
+
+ testTransitionFromConnectingToDisconnected(mA2dpProfile, mLeAudioProfile,
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED, connectTimeoutString);
+ testTransitionFromConnectingToDisconnected(mHearingAidProfile, mLeAudioProfile,
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED, connectTimeoutString);
+ testTransitionFromConnectingToDisconnected(mHfpProfile, mLeAudioProfile,
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED, connectTimeoutString);
+ testTransitionFromConnectingToDisconnected(mLeAudioProfile, mA2dpProfile,
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED, connectTimeoutString);
+ }
+
+ @Test
+ public void onProfileStateChanged_testConnectingToDisconnected_policyForbidden_noProblem() {
+ testTransitionFromConnectingToDisconnected(mA2dpProfile, mLeAudioProfile,
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, null);
+ testTransitionFromConnectingToDisconnected(mHearingAidProfile, mLeAudioProfile,
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, null);
+ testTransitionFromConnectingToDisconnected(mHfpProfile, mLeAudioProfile,
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, null);
+ testTransitionFromConnectingToDisconnected(mLeAudioProfile, mA2dpProfile,
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, null);
+ }
+
+ @Test
+ public void onProfileStateChanged_testConnectingToDisconnected_policyUnknown_noProblem() {
+ testTransitionFromConnectingToDisconnected(mA2dpProfile, mLeAudioProfile,
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN, null);
+ testTransitionFromConnectingToDisconnected(mHearingAidProfile, mLeAudioProfile,
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN, null);
+ testTransitionFromConnectingToDisconnected(mHfpProfile, mLeAudioProfile,
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN, null);
+ testTransitionFromConnectingToDisconnected(mLeAudioProfile, mA2dpProfile,
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN, null);
+ }
+
@Test
public void getConnectionSummary_testProfilesInactive_returnPairing() {
// Arrange:
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 418011a..27c8cdf 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3056,11 +3056,11 @@
final int key = makeKey(type, userId);
boolean success = false;
- boolean isNewSetting = false;
+ boolean wasUnsetNonPredefinedSetting = false;
SettingsState settingsState = peekSettingsStateLocked(key);
if (settingsState != null) {
- if (!settingsState.hasSetting(name)) {
- isNewSetting = true;
+ if (!isSettingPreDefined(name, type) && !settingsState.hasSetting(name)) {
+ wasUnsetNonPredefinedSetting = true;
}
success = settingsState.insertSettingLocked(name, value,
tag, makeDefault, forceNonSystemPackage, packageName,
@@ -3073,9 +3073,9 @@
if (forceNotify || success) {
notifyForSettingsChange(key, name);
- if (isNewSetting && !isSettingPreDefined(name, type)) {
- // Increment the generation number for all null settings because a new
- // non-predefined setting has been inserted
+ if (wasUnsetNonPredefinedSetting) {
+ // Increment the generation number for all non-predefined, unset settings,
+ // because a new non-predefined setting has been inserted
mGenerationRegistry.incrementGenerationForUnsetSettings(key);
}
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 71f438e..6176c61 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -181,8 +181,8 @@
<!-- Doze mode temp whitelisting for notification dispatching. -->
<uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" />
- <!-- Tag user-initiated PendingIntent invocations as "interactive" when appropriate -->
- <uses-permission android:name="android.permission.COMPONENT_OPTION_INTERACTIVE" />
+ <!-- Adjust delivery policies for broadcast intents -->
+ <uses-permission android:name="android.permission.BROADCAST_OPTION_INTERACTIVE" />
<!-- Listen for keyboard attachment / detachment -->
<uses-permission android:name="android.permission.TABLET_MODE" />
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 17a94b86..296c2ae 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -419,7 +419,7 @@
internal val delegate: AnimationDelegate
init {
- delegate = AnimationDelegate(controller, callback, launchAnimator, listener)
+ delegate = AnimationDelegate(controller, callback, listener, launchAnimator)
}
@BinderThread
@@ -446,10 +446,10 @@
constructor(
private val controller: Controller,
private val callback: Callback,
- /** The animator to use to animate the window launch. */
- private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
/** Listener for animation lifecycle events. */
- private val listener: Listener? = null
+ private val listener: Listener? = null,
+ /** The animator to use to animate the window launch. */
+ private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR
) : RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> {
private val launchContainer = controller.launchContainer
private val context = launchContainer.context
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
index 2903288..f0a8211 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
@@ -19,14 +19,15 @@
import android.graphics.fonts.Font
import android.graphics.fonts.FontVariationAxis
import android.util.MathUtils
+import android.util.MathUtils.abs
+import java.lang.Float.max
+import java.lang.Float.min
private const val TAG_WGHT = "wght"
private const val TAG_ITAL = "ital"
-private const val FONT_WEIGHT_MAX = 1000f
-private const val FONT_WEIGHT_MIN = 0f
-private const val FONT_WEIGHT_ANIMATION_STEP = 10f
private const val FONT_WEIGHT_DEFAULT_VALUE = 400f
+private const val FONT_WEIGHT_ANIMATION_FRAME_COUNT = 100
private const val FONT_ITALIC_MAX = 1f
private const val FONT_ITALIC_MIN = 0f
@@ -118,14 +119,17 @@
lerp(startAxes, endAxes) { tag, startValue, endValue ->
when (tag) {
// TODO: Good to parse 'fvar' table for retrieving default value.
- TAG_WGHT ->
- adjustWeight(
+ TAG_WGHT -> {
+ adaptiveAdjustWeight(
MathUtils.lerp(
startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
progress
- )
+ ),
+ startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
+ endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
)
+ }
TAG_ITAL ->
adjustItalic(
MathUtils.lerp(
@@ -205,10 +209,14 @@
return result
}
- // For the performance reasons, we animate weight with FONT_WEIGHT_ANIMATION_STEP. This helps
+ // For the performance reasons, we animate weight with adaptive step. This helps
// Cache hit ratio in the Skia glyph cache.
- private fun adjustWeight(value: Float) =
- coerceInWithStep(value, FONT_WEIGHT_MIN, FONT_WEIGHT_MAX, FONT_WEIGHT_ANIMATION_STEP)
+ // The reason we don't use fix step is because the range of weight axis is not normalized,
+ // some are from 50 to 100, others are from 0 to 1000, so we cannot give a constant proper step
+ private fun adaptiveAdjustWeight(value: Float, start: Float, end: Float): Float {
+ val step = max(abs(end - start) / FONT_WEIGHT_ANIMATION_FRAME_COUNT, 1F)
+ return coerceInWithStep(value, min(start, end), max(start, end), step)
+ }
// For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps
// Cache hit ratio in the Skia glyph cache.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index a08b598..7fe94d3 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -190,6 +190,7 @@
* @param textSize an optional font size.
* @param colors an optional colors array that must be the same size as numLines passed to
* the TextInterpolator
+ * @param strokeWidth an optional paint stroke width
* @param animate an optional boolean indicating true for showing style transition as animation,
* false for immediate style transition. True by default.
* @param duration an optional animation duration in milliseconds. This is ignored if animate is
@@ -201,6 +202,7 @@
weight: Int = -1,
textSize: Float = -1f,
color: Int? = null,
+ strokeWidth: Float = -1f,
animate: Boolean = true,
duration: Long = -1L,
interpolator: TimeInterpolator? = null,
@@ -254,6 +256,9 @@
if (color != null) {
textInterpolator.targetPaint.color = color
}
+ if (strokeWidth >= 0F) {
+ textInterpolator.targetPaint.strokeWidth = strokeWidth
+ }
textInterpolator.onTargetPaintModified()
if (animate) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index 468a8b1..3eb7fd8 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -473,6 +473,7 @@
// TODO(172943390): Add other interpolation or support custom interpolator.
out.textSize = MathUtils.lerp(from.textSize, to.textSize, progress)
out.color = ColorUtils.blendARGB(from.color, to.color, progress)
+ out.strokeWidth = MathUtils.lerp(from.strokeWidth, to.strokeWidth, progress)
}
// Shape the text and stores the result to out argument.
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
index f64ea45..459a38e 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
@@ -25,6 +25,7 @@
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
+import java.util.regex.Pattern
import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.UElement
@@ -36,22 +37,38 @@
override fun createUastHandler(context: JavaContext): UElementHandler {
return object : UElementHandler() {
override fun visitAnnotation(node: UAnnotation) {
- if (node.qualifiedName !in DEMOTING_ANNOTATION) {
- return
+ // Annotations having int bugId field
+ if (node.qualifiedName in DEMOTING_ANNOTATION_BUG_ID) {
+ val bugId = node.findAttributeValue("bugId")!!.evaluate() as Int
+ if (bugId <= 0) {
+ val location = context.getLocation(node)
+ val message = "Please attach a bug id to track demoted test"
+ context.report(ISSUE, node, location, message)
+ }
}
- val bugId = node.findAttributeValue("bugId")!!.evaluate() as Int
- if (bugId <= 0) {
- val location = context.getLocation(node)
- val message = "Please attach a bug id to track demoted test"
- context.report(ISSUE, node, location, message)
+ // @Ignore has a String field for reason
+ if (node.qualifiedName == DEMOTING_ANNOTATION_IGNORE) {
+ val reason = node.findAttributeValue("value")!!.evaluate() as String
+ val bugPattern = Pattern.compile("b/\\d+")
+ if (!bugPattern.matcher(reason).find()) {
+ val location = context.getLocation(node)
+ val message = "Please attach a bug (e.g. b/123) to track demoted test"
+ context.report(ISSUE, node, location, message)
+ }
}
}
}
}
companion object {
- val DEMOTING_ANNOTATION =
- listOf("androidx.test.filters.FlakyTest", "android.platform.test.annotations.FlakyTest")
+ val DEMOTING_ANNOTATION_BUG_ID =
+ listOf(
+ "androidx.test.filters.FlakyTest",
+ "android.platform.test.annotations.FlakyTest",
+ "android.platform.test.rule.PlatinumRule.Platinum",
+ )
+
+ const val DEMOTING_ANNOTATION_IGNORE = "org.junit.Ignore"
@JvmField
val ISSUE: Issue =
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
index 557c300..63eb263 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
@@ -127,6 +127,139 @@
)
}
+ @Test
+ fun testExcludeDevices_withBugId() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.platform.test.rule.PlatinumRule.Platinum;
+
+ @Platinum(devices = "foo,bar", bugId = 123)
+ public class TestClass {
+ public void testCase() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(DemotingTestWithoutBugDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun testExcludeDevices_withoutBugId() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.platform.test.rule.PlatinumRule.Platinum;
+
+ @Platinum(devices = "foo,bar")
+ public class TestClass {
+ public void testCase() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(DemotingTestWithoutBugDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:4: Warning: Please attach a bug id to track demoted test [DemotingTestWithoutBug]
+ @Platinum(devices = "foo,bar")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ }
+
+ @Test
+ fun testIgnore_withBug() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import org.junit.Ignore;
+
+ @Ignore("Blocked by b/123.")
+ public class TestClass {
+ public void testCase() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(DemotingTestWithoutBugDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun testIgnore_withoutBug() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import org.junit.Ignore;
+
+ @Ignore
+ public class TestClass {
+ public void testCase() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(DemotingTestWithoutBugDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:4: Warning: Please attach a bug (e.g. b/123) to track demoted test [DemotingTestWithoutBug]
+ @Ignore
+ ~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import org.junit.Ignore;
+
+ @Ignore("Not ready")
+ public class TestClass {
+ public void testCase() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(DemotingTestWithoutBugDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:4: Warning: Please attach a bug (e.g. b/123) to track demoted test [DemotingTestWithoutBug]
+ @Ignore("Not ready")
+ ~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ }
+
private val filtersFlakyTestStub: TestFile =
java(
"""
@@ -147,5 +280,34 @@
}
"""
)
- private val stubs = arrayOf(filtersFlakyTestStub, annotationsFlakyTestStub)
+ private val annotationsPlatinumStub: TestFile =
+ java(
+ """
+ package android.platform.test.rule;
+
+ public class PlatinumRule {
+ public @interface Platinum {
+ String devices();
+ int bugId() default -1;
+ }
+ }
+ """
+ )
+ private val annotationsIgnoreStub: TestFile =
+ java(
+ """
+ package org.junit;
+
+ public @interface Ignore {
+ String value() default "";
+ }
+ """
+ )
+ private val stubs =
+ arrayOf(
+ filtersFlakyTestStub,
+ annotationsFlakyTestStub,
+ annotationsPlatinumStub,
+ annotationsIgnoreStub
+ )
}
diff --git a/packages/SystemUI/res-keyguard/values-land/dimens.xml b/packages/SystemUI/res-keyguard/values-land/dimens.xml
index f1aa544..a4e7a5f 100644
--- a/packages/SystemUI/res-keyguard/values-land/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-land/dimens.xml
@@ -27,6 +27,4 @@
<integer name="scaled_password_text_size">26</integer>
<dimen name="bouncer_user_switcher_y_trans">@dimen/status_bar_height</dimen>
- <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
- <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index caf3233..1f44f05 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -127,9 +127,7 @@
<dimen name="bouncer_user_switcher_item_padding_vertical">10dp</dimen>
<dimen name="bouncer_user_switcher_item_padding_horizontal">12dp</dimen>
<dimen name="bouncer_user_switcher_header_padding_end">44dp</dimen>
- <dimen name="bouncer_user_switcher_y_trans">0dp</dimen>
- <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
- <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
+ <dimen name="bouncer_user_switcher_y_trans">80dp</dimen>
<!-- 2 * the margin + size should equal the plus_margin -->
<dimen name="user_switcher_icon_large_margin">16dp</dimen>
diff --git a/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml b/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml
new file mode 100644
index 0000000..d123caf
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml
@@ -0,0 +1,12 @@
+<vector android:height="11dp" android:viewportHeight="12"
+ android:viewportWidth="22" android:width="20.166666dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <group>
+ <clip-path android:pathData="M0,0.5h22v11h-22z"/>
+ <path android:fillColor="#231F20" android:pathData="M6.397,9.908H0V11.5H6.397V9.908Z"/>
+ <path android:fillColor="#231F20" android:pathData="M14.199,9.908H7.801V11.5H14.199V9.908Z"/>
+ <path android:fillColor="#231F20" android:pathData="M11.858,0.5H10.142V6.434H11.858V0.5Z"/>
+ <path android:fillColor="#231F20" android:pathData="M8.348,7.129L3.885,2.975L3.823,2.932L2.668,4.003L2.621,4.046L7.084,8.2L7.146,8.243L8.301,7.172L8.348,7.129Z"/>
+ <path android:fillColor="#231F20" android:pathData="M18.224,2.975L18.177,2.932L13.653,7.129L14.807,8.2L14.854,8.243L19.379,4.046L18.224,2.975Z"/>
+ <path android:fillColor="#231F20" android:pathData="M22,9.908H15.603V11.5H22V9.908Z"/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 4f38e60..908aac4 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -66,4 +66,8 @@
<dimen name="controls_header_horizontal_padding">12dp</dimen>
<dimen name="controls_content_margin_horizontal">16dp</dimen>
+
+ <!-- Bouncer user switcher margins -->
+ <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
index b98165f..ca62d28 100644
--- a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
@@ -21,6 +21,6 @@
<!-- Space between status view and notification shelf -->
<dimen name="keyguard_status_view_bottom_margin">70dp</dimen>
<dimen name="keyguard_clock_top_margin">80dp</dimen>
- <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">186dp</dimen>
- <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">110dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">155dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">85dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index d4ebd10..5d188fe 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -206,6 +206,11 @@
<color name="control_thumbnail_shadow_color">@*android:color/black</color>
<color name="controls_task_view_bg">#CC191C1D</color>
+ <!-- Keyboard backlight indicator-->
+ <color name="backlight_indicator_step_filled">#F6E388</color>
+ <color name="backlight_indicator_step_empty">#494740</color>
+ <color name="backlight_indicator_background">#32302A</color>
+
<!-- Docked misalignment message -->
<color name="misalignment_text_color">#F28B82</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e65c327..8f90724 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -782,7 +782,7 @@
<!-- Duration in milliseconds of the dream in complications fade-in animation. -->
<integer name="config_dreamOverlayInComplicationsDurationMs">250</integer>
<!-- Duration in milliseconds of the y-translation animation when entering a dream -->
- <integer name="config_dreamOverlayInTranslationYDurationMs">917</integer>
+ <integer name="config_dreamOverlayInTranslationYDurationMs">1167</integer>
<!-- Delay in milliseconds before switching to the dock user and dreaming if a secondary user is
active when the device is locked and docked. 0 indicates disabled. Default is 1 minute. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index aba3fc4..e78be4c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -196,9 +196,6 @@
<!-- Increased height of a small notification in the status bar -->
<dimen name="notification_min_height_increased">146dp</dimen>
- <!-- Increased height of a collapsed media notification in the status bar -->
- <dimen name="notification_min_height_media">160dp</dimen>
-
<!-- Height of a small notification in the status bar which was used before android N -->
<dimen name="notification_min_height_legacy">64dp</dimen>
@@ -1701,6 +1698,19 @@
<dimen name="media_output_broadcast_info_summary_height">20dp</dimen>
<dimen name="media_output_broadcast_info_edit">18dp</dimen>
+ <!-- Keyboard backlight indicator-->
+ <dimen name="backlight_indicator_root_corner_radius">48dp</dimen>
+ <dimen name="backlight_indicator_root_vertical_padding">8dp</dimen>
+ <dimen name="backlight_indicator_root_horizontal_padding">4dp</dimen>
+ <dimen name="backlight_indicator_icon_width">22dp</dimen>
+ <dimen name="backlight_indicator_icon_height">11dp</dimen>
+ <dimen name="backlight_indicator_icon_left_margin">2dp</dimen>
+ <dimen name="backlight_indicator_step_width">52dp</dimen>
+ <dimen name="backlight_indicator_step_height">40dp</dimen>
+ <dimen name="backlight_indicator_step_horizontal_margin">4dp</dimen>
+ <dimen name="backlight_indicator_step_small_radius">4dp</dimen>
+ <dimen name="backlight_indicator_step_large_radius">48dp</dimen>
+
<!-- Broadcast dialog -->
<dimen name="broadcast_dialog_title_img_margin_top">18dp</dimen>
<dimen name="broadcast_dialog_title_text_size">24sp</dimen>
@@ -1749,4 +1759,9 @@
it is long-pressed.
-->
<dimen name="keyguard_long_press_settings_popup_vertical_offset">96dp</dimen>
+
+
+ <!-- Bouncer user switcher margins -->
+ <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f4b3b87..a02c429 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -191,8 +191,8 @@
<!-- Power menu item for taking a screenshot [CHAR LIMIT=20]-->
<string name="global_action_screenshot">Screenshot</string>
- <!-- Message shown in power menu when smart lock has been disabled [CHAR_LIMIT=NONE] -->
- <string name="global_action_smart_lock_disabled">Smart Lock disabled</string>
+ <!-- Message shown in power menu when Extend Unlock has been disabled [CHAR_LIMIT=NONE] -->
+ <string name="global_action_smart_lock_disabled">Extend Unlock disabled</string>
<!-- text to show in place of RemoteInput images when they cannot be shown.
[CHAR LIMIT=50] -->
@@ -2431,7 +2431,7 @@
<!-- Shows in a dialog presented to the user to authorize this app to display a Device controls
panel (embedded activity) instead of controls rendered by SystemUI [CHAR LIMIT=NONE] -->
- <string name="controls_panel_authorization">When you add <xliff:g id="appName" example="My app">%s</xliff:g>, it can add controls and content to this panel. In some apps, you can choose which controls show up here.</string>
+ <string name="controls_panel_authorization"><xliff:g id="appName" example="My app">%s</xliff:g>can choose which controls and content show here.</string>
<!-- Shows in a dialog presented to the user to authorize this app removal from a Device
controls panel [CHAR LIMIT=NONE] -->
@@ -2491,7 +2491,7 @@
<!-- Title of the dialog to control certain devices from lock screen without auth [CHAR LIMIT=NONE] -->
<string name="controls_settings_trivial_controls_dialog_title">Control devices from lock screen?</string>
<!-- Message of the dialog to control certain devices from lock screen without auth [CHAR LIMIT=NONE] -->
- <string name="controls_settings_trivial_controls_dialog_message">You can control some devices without unlocking your phone or tablet.\n\nYour device app determines which devices can be controlled in this way.</string>
+ <string name="controls_settings_trivial_controls_dialog_message">You can control some devices without unlocking your phone or tablet. Your device app determines which devices can be controlled in this way.</string>
<!-- Neutral button title of the controls dialog [CHAR LIMIT=NONE] -->
<string name="controls_settings_dialog_neutral_button">No thanks</string>
<!-- Positive button title of the controls dialog [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
index 9a581aa..482158e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
@@ -91,6 +91,22 @@
val sampledRegion = calculateSampledRegion(sampledView)
val regions = ArrayList<RectF>()
val sampledRegionWithOffset = convertBounds(sampledRegion)
+
+ if (
+ sampledRegionWithOffset.left < 0.0 ||
+ sampledRegionWithOffset.right > 1.0 ||
+ sampledRegionWithOffset.top < 0.0 ||
+ sampledRegionWithOffset.bottom > 1.0
+ ) {
+ android.util.Log.e(
+ "RegionSampler",
+ "view out of bounds: $sampledRegion | " +
+ "screen width: ${displaySize.x}, screen height: ${displaySize.y}",
+ Exception()
+ )
+ return
+ }
+
regions.add(sampledRegionWithOffset)
wallpaperManager?.removeOnColorsChangedListener(this)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 359da13..5b27c40 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -29,8 +29,11 @@
import android.annotation.DrawableRes;
import android.annotation.SuppressLint;
import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
@@ -86,6 +89,7 @@
private RotationButton mRotationButton;
private boolean mIsRecentsAnimationRunning;
+ private boolean mDocked;
private boolean mHomeRotationEnabled;
private int mLastRotationSuggestion;
private boolean mPendingRotationSuggestion;
@@ -123,6 +127,12 @@
() -> mPendingRotationSuggestion = false;
private Animator mRotateHideAnimator;
+ private final BroadcastReceiver mDockedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ updateDockedState(intent);
+ }
+ };
private final IRotationWatcher.Stub mRotationWatcher = new IRotationWatcher.Stub() {
@Override
@@ -136,7 +146,8 @@
// The isVisible check makes the rotation button disappear when we are not locked
// (e.g. for tabletop auto-rotate).
if (rotationLocked || mRotationButton.isVisible()) {
- if (shouldOverrideUserLockPrefs(rotation) && rotationLocked) {
+ // Do not allow a change in rotation to set user rotation when docked.
+ if (shouldOverrideUserLockPrefs(rotation) && rotationLocked && !mDocked) {
setRotationLockedAtAngle(rotation);
}
setRotateSuggestionButtonState(false /* visible */, true /* forced */);
@@ -214,6 +225,10 @@
}
mListenersRegistered = true;
+
+ updateDockedState(mContext.registerReceiver(mDockedReceiver,
+ new IntentFilter(Intent.ACTION_DOCK_EVENT)));
+
try {
WindowManagerGlobal.getWindowManagerService()
.watchRotation(mRotationWatcher, DEFAULT_DISPLAY);
@@ -234,6 +249,8 @@
}
mListenersRegistered = false;
+
+ mContext.unregisterReceiver(mDockedReceiver);
try {
WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher);
} catch (RemoteException e) {
@@ -345,6 +362,15 @@
updateRotationButtonStateInOverview();
}
+ private void updateDockedState(Intent intent) {
+ if (intent == null) {
+ return;
+ }
+
+ mDocked = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED)
+ != Intent.EXTRA_DOCK_STATE_UNDOCKED;
+ }
+
private void updateRotationButtonStateInOverview() {
if (mIsRecentsAnimationRunning && !mHomeRotationEnabled) {
setRotateSuggestionButtonState(false, true /* hideImmediately */);
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index 8323d09..f005bab 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -23,6 +23,8 @@
import dagger.Binds
import dagger.Module
import dagger.Provides
+import dagger.multibindings.IntoSet
+import javax.inject.Named
@Module(includes = [
FeatureFlagsDebugStartableModule::class,
@@ -35,7 +37,8 @@
abstract fun bindsFeatureFlagDebug(impl: FeatureFlagsDebug): FeatureFlags
@Binds
- abstract fun bindsRestarter(debugRestarter: FeatureFlagsDebugRestarter): Restarter
+ @IntoSet
+ abstract fun bindsScreenIdleCondition(impl: ScreenIdleCondition): ConditionalRestarter.Condition
@Module
companion object {
@@ -44,5 +47,10 @@
fun provideFlagManager(context: Context, @Main handler: Handler): FlagManager {
return FlagManager(context, handler)
}
+
+ @JvmStatic
+ @Provides
+ @Named(ConditionalRestarter.RESTART_DELAY)
+ fun provideRestartDelaySec(): Long = 1
}
}
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index 87beff7..927d4604b 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -18,6 +18,9 @@
import dagger.Binds
import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoSet
+import javax.inject.Named
@Module(includes = [
FeatureFlagsReleaseStartableModule::class,
@@ -29,5 +32,18 @@
abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
@Binds
- abstract fun bindsRestarter(debugRestarter: FeatureFlagsReleaseRestarter): Restarter
+ @IntoSet
+ abstract fun bindsScreenIdleCondition(impl: ScreenIdleCondition): ConditionalRestarter.Condition
+
+ @Binds
+ @IntoSet
+ abstract fun bindsPluggedInCondition(impl: PluggedInCondition): ConditionalRestarter.Condition
+
+ @Module
+ companion object {
+ @JvmStatic
+ @Provides
+ @Named(ConditionalRestarter.RESTART_DELAY)
+ fun provideRestartDelaySec(): Long = 30
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 92ee373..4aaa566 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -21,6 +21,7 @@
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Resources
+import android.graphics.Rect
import android.text.format.DateFormat
import android.util.TypedValue
import android.view.View
@@ -119,10 +120,6 @@
private val mLayoutChangedListener =
object : View.OnLayoutChangeListener {
- private var currentSmallClockView: View? = null
- private var currentLargeClockView: View? = null
- private var currentSmallClockLocation = IntArray(2)
- private var currentLargeClockLocation = IntArray(2)
override fun onLayoutChange(
view: View?,
@@ -135,6 +132,8 @@
oldRight: Int,
oldBottom: Int
) {
+ view?.removeOnLayoutChangeListener(this)
+
val parent = (view?.parent) as FrameLayout
// don't pass in negative bounds when clocks are in transition state
@@ -142,31 +141,12 @@
return
}
- // SMALL CLOCK
- if (parent.id == R.id.lockscreen_clock_view) {
- // view bounds have changed due to clock size changing (i.e. different character
- // widths)
- // AND/OR the view has been translated when transitioning between small and
- // large clock
- if (
- view != currentSmallClockView ||
- !view.locationOnScreen.contentEquals(currentSmallClockLocation)
- ) {
- currentSmallClockView = view
- currentSmallClockLocation = view.locationOnScreen
- updateRegionSampler(view)
- }
- }
- // LARGE CLOCK
- else if (parent.id == R.id.lockscreen_clock_view_large) {
- if (
- view != currentLargeClockView ||
- !view.locationOnScreen.contentEquals(currentLargeClockLocation)
- ) {
- currentLargeClockView = view
- currentLargeClockLocation = view.locationOnScreen
- updateRegionSampler(view)
- }
+ val currentViewRect = Rect(left, top, right, bottom)
+ val oldViewRect = Rect(oldLeft, oldTop, oldRight, oldBottom)
+
+ if (currentViewRect.width() != oldViewRect.width() ||
+ currentViewRect.height() != oldViewRect.height()) {
+ updateRegionSampler(view)
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 67e3400..0394754 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -217,9 +217,11 @@
private void animate(float progress) {
Interpolator standardDecelerate = Interpolators.STANDARD_DECELERATE;
Interpolator legacyDecelerate = Interpolators.LEGACY_DECELERATE;
+ float standardProgress = standardDecelerate.getInterpolation(progress);
mBouncerMessageView.setTranslationY(
- mYTrans - mYTrans * standardDecelerate.getInterpolation(progress));
+ mYTrans - mYTrans * standardProgress);
+ mBouncerMessageView.setAlpha(standardProgress);
for (int i = 0; i < mViews.length; i++) {
View[] row = mViews[i];
@@ -236,7 +238,7 @@
view.setAlpha(scaledProgress);
int yDistance = mYTrans + mYTransOffset * i;
view.setTranslationY(
- yDistance - (yDistance * standardDecelerate.getInterpolation(progress)));
+ yDistance - (yDistance * standardProgress));
if (view instanceof NumPadAnimationListener) {
((NumPadAnimationListener) view).setProgress(scaledProgress);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 66d5d09..ba5a8c9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -39,6 +39,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
@@ -1067,10 +1068,14 @@
int yTranslation = mResources.getDimensionPixelSize(R.dimen.disappear_y_translation);
+ AnimatorSet anims = new AnimatorSet();
ObjectAnimator yAnim = ObjectAnimator.ofFloat(mView, View.TRANSLATION_Y, yTranslation);
- yAnim.setInterpolator(Interpolators.STANDARD_ACCELERATE);
- yAnim.setDuration(500);
- yAnim.start();
+ ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA,
+ 0f);
+
+ anims.setInterpolator(Interpolators.STANDARD_ACCELERATE);
+ anims.playTogether(alphaAnim, yAnim);
+ anims.start();
}
private void setupUserSwitcher() {
@@ -1220,8 +1225,7 @@
constraintSet.connect(rightElement, LEFT, leftElement, RIGHT);
constraintSet.connect(rightElement, RIGHT, PARENT_ID, RIGHT);
constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP);
- constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, PARENT_ID, BOTTOM,
- yTrans);
+ constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, PARENT_ID, BOTTOM);
constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
constraintSet.setHorizontalChainStyle(mUserSwitcherViewGroup.getId(), CHAIN_SPREAD);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 06258b2..98866c6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -127,6 +127,7 @@
private View.OnKeyListener mOnKeyListener = (v, keyCode, event) -> interceptMediaKey(event);
private ActivityStarter.OnDismissAction mDismissAction;
private Runnable mCancelAction;
+ private boolean mWillRunDismissFromKeyguard;
private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
@@ -262,8 +263,10 @@
// If there's a pending runnable because the user interacted with a widget
// and we're leaving keyguard, then run it.
boolean deferKeyguardDone = false;
+ mWillRunDismissFromKeyguard = false;
if (mDismissAction != null) {
deferKeyguardDone = mDismissAction.onDismiss();
+ mWillRunDismissFromKeyguard = mDismissAction.willRunAnimationOnKeyguard();
mDismissAction = null;
mCancelAction = null;
}
@@ -527,6 +530,13 @@
}
/**
+ * @return will the dismissal run from the keyguard layout (instead of from bouncer)
+ */
+ public boolean willRunDismissFromKeyguard() {
+ return mWillRunDismissFromKeyguard;
+ }
+
+ /**
* Remove any dismiss action or cancel action that was set.
*/
public void cancelDismissAction() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 30e2a0b..aaf6307 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -717,6 +717,12 @@
if (mKeyguardGoingAway) {
updateFaceListeningState(BIOMETRIC_ACTION_STOP,
FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onKeyguardGoingAway();
+ }
+ }
}
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
}
@@ -2453,8 +2459,10 @@
Boolean isFaceEnrolled = mFaceManager != null && !mFaceSensorProperties.isEmpty()
&& mBiometricEnabledForUser.get(userId)
&& mAuthController.isFaceAuthEnrolled(userId);
+ if (mIsFaceEnrolled != isFaceEnrolled) {
+ mLogger.logFaceEnrolledUpdated(mIsFaceEnrolled, isFaceEnrolled);
+ }
mIsFaceEnrolled = isFaceEnrolled;
- mLogger.logFaceEnrolledUpdated(mIsFaceEnrolled, isFaceEnrolled);
}
public boolean isFaceSupported() {
@@ -3126,13 +3134,14 @@
@VisibleForTesting
boolean isUnlockWithFingerprintPossible(int userId) {
// TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
- boolean fpEnrolled = mFpm != null
+ boolean newFpEnrolled = mFpm != null
&& !mFingerprintSensorProperties.isEmpty()
&& !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId);
- mLogger.logFpEnrolledUpdated(userId,
- mIsUnlockWithFingerprintPossible.getOrDefault(userId, false),
- fpEnrolled);
- mIsUnlockWithFingerprintPossible.put(userId, fpEnrolled);
+ Boolean oldFpEnrolled = mIsUnlockWithFingerprintPossible.getOrDefault(userId, false);
+ if (oldFpEnrolled != newFpEnrolled) {
+ mLogger.logFpEnrolledUpdated(userId, oldFpEnrolled, newFpEnrolled);
+ }
+ mIsUnlockWithFingerprintPossible.put(userId, newFpEnrolled);
return mIsUnlockWithFingerprintPossible.get(userId);
}
@@ -3680,7 +3689,9 @@
* Register to receive notifications about general keyguard information
* (see {@link KeyguardUpdateMonitorCallback}.
*
- * @param callback The callback to register
+ * @param callback The callback to register. Stay away from passing anonymous instances
+ * as they will likely be dereferenced. Ensure that the callback is a class
+ * field to persist it.
*/
public void registerCallback(KeyguardUpdateMonitorCallback callback) {
Assert.isMainThread();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 0d4889a..feff216 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -317,4 +317,9 @@
* Called when the non-strong biometric state changed.
*/
public void onNonStrongBiometricAllowedChanged(int userId) { }
+
+ /**
+ * Called when keyguard is going away or not going away.
+ */
+ public void onKeyguardGoingAway() { }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
index 54f933a..53a421d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
@@ -29,6 +29,7 @@
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.settings.SystemSettings
+import kotlin.math.roundToInt
/** The Dialog that contains a seekbar for changing the font size. */
class FontScalingDialog(context: Context, private val systemSettings: SystemSettings) :
@@ -56,6 +57,16 @@
doneButton = requireViewById(com.android.internal.R.id.button1)
seekBarWithIconButtonsView = requireViewById(R.id.font_scaling_slider)
+ val labelArray = arrayOfNulls<String>(strEntryValues.size)
+ for (i in strEntryValues.indices) {
+ labelArray[i] =
+ context.resources.getString(
+ com.android.settingslib.R.string.font_scale_percentage,
+ (strEntryValues[i].toFloat() * 100).roundToInt()
+ )
+ }
+ seekBarWithIconButtonsView.setProgressStateLabels(labelArray)
+
seekBarWithIconButtonsView.setMax((strEntryValues).size - 1)
val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, 1.0f)
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 2501be9..e049ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -351,9 +351,20 @@
}
private void animateFromMinimized() {
- mIsMinimized = false;
- setExpandedView();
- animateIn();
+ if (mEnterAnimator != null && mEnterAnimator.isRunning()) {
+ mEnterAnimator.cancel();
+ }
+ mEnterAnimator = mView.getMinimizedFadeoutAnimation();
+ mEnterAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mIsMinimized = false;
+ setExpandedView();
+ animateIn();
+ }
+ });
+ mEnterAnimator.start();
}
private String getAccessibilityAnnouncement(ClipboardModel.Type type) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index f372bb4..28c57d3 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -21,6 +21,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
@@ -286,6 +287,20 @@
mActionChips.clear();
}
+ Animator getMinimizedFadeoutAnimation() {
+ ObjectAnimator anim = ObjectAnimator.ofFloat(mMinimizedPreview, "alpha", 1, 0);
+ anim.setDuration(66);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mMinimizedPreview.setVisibility(View.GONE);
+ mMinimizedPreview.setAlpha(1);
+ }
+ });
+ return anim;
+ }
+
Animator getEnterAnimation() {
if (mAccessibilityManager.isEnabled()) {
mDismissButton.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
index 24f6296..de3a990 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
@@ -45,6 +45,7 @@
private SeekBar mSeekbar;
private SeekBarChangeListener mSeekBarListener = new SeekBarChangeListener();
+ private String[] mStateLabels = null;
public SeekBarWithIconButtonsView(Context context) {
this(context, null);
@@ -132,6 +133,30 @@
}
/**
+ * Stores the String array we would like to use for describing the state of seekbar progress
+ * and updates the state description with current progress.
+ *
+ * @param labels The state descriptions to be announced for each progress.
+ */
+ public void setProgressStateLabels(String[] labels) {
+ mStateLabels = labels;
+ if (mStateLabels != null) {
+ setSeekbarStateDescription();
+ }
+ }
+
+ /**
+ * Sets the state of seekbar based on current progress. The progress of seekbar is
+ * corresponding to the index of the string array. If the progress is larger than or equals
+ * to the length of the array, the state description is set to an empty string.
+ */
+ private void setSeekbarStateDescription() {
+ mSeekbar.setStateDescription(
+ (mSeekbar.getProgress() < mStateLabels.length)
+ ? mStateLabels[mSeekbar.getProgress()] : "");
+ }
+
+ /**
* Sets a onSeekbarChangeListener to the seekbar in the layout.
* We update the Start Icon and End Icon if needed when the seekbar progress is changed.
*/
@@ -173,6 +198,9 @@
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (mStateLabels != null) {
+ setSeekbarStateDescription();
+ }
if (mOnSeekBarChangeListener != null) {
mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index 3555d0a..2d37c29 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -173,12 +173,6 @@
fun removeFavorites(componentName: ComponentName): Boolean
/**
- * Checks if the favorites can be removed. You can't remove components from the preferred list.
- * @param componentName the name of the service that provides the [Control]
- */
- fun canRemoveFavorites(componentName: ComponentName): Boolean
-
- /**
* Replaces the favorites for the given structure.
*
* Calling this method will eliminate the previous selection of favorites and replace it with a
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index a171f43..ac1150e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -38,6 +38,7 @@
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.dagger.SysUISingleton
@@ -57,16 +58,17 @@
@SysUISingleton
class ControlsControllerImpl @Inject constructor (
- private val context: Context,
- @Background private val executor: DelayableExecutor,
- private val uiController: ControlsUiController,
- private val bindingController: ControlsBindingController,
- private val listingController: ControlsListingController,
- private val userFileManager: UserFileManager,
- private val userTracker: UserTracker,
- private val authorizedPanelsRepository: AuthorizedPanelsRepository,
- optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
- dumpManager: DumpManager,
+ private val context: Context,
+ @Background private val executor: DelayableExecutor,
+ private val uiController: ControlsUiController,
+ private val selectedComponentRepository: SelectedComponentRepository,
+ private val bindingController: ControlsBindingController,
+ private val listingController: ControlsListingController,
+ private val userFileManager: UserFileManager,
+ private val userTracker: UserTracker,
+ private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+ optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
+ dumpManager: DumpManager,
) : Dumpable, ControlsController {
companion object {
@@ -499,17 +501,14 @@
}
}
- override fun canRemoveFavorites(componentName: ComponentName): Boolean =
- !authorizedPanelsRepository.getPreferredPackages().contains(componentName.packageName)
-
override fun removeFavorites(componentName: ComponentName): Boolean {
if (!confirmAvailability()) return false
- if (!canRemoveFavorites(componentName)) return false
executor.execute {
- Favorites.removeStructures(componentName)
+ if (Favorites.removeStructures(componentName)) {
+ persistenceWrapper.storeFavorites(Favorites.getAllStructures())
+ }
authorizedPanelsRepository.removeAuthorizedPanels(setOf(componentName.packageName))
- persistenceWrapper.storeFavorites(Favorites.getAllStructures())
}
return true
}
@@ -576,7 +575,9 @@
}
override fun setPreferredSelection(selectedItem: SelectedItem) {
- uiController.updatePreferences(selectedItem)
+ selectedComponentRepository.setSelectedComponent(
+ SelectedComponentRepository.SelectedComponent(selectedItem)
+ )
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index d949d11..2af49aa 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -36,6 +36,8 @@
import com.android.systemui.controls.management.ControlsRequestDialog
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
import com.android.systemui.controls.panels.AuthorizedPanelsRepositoryImpl
+import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.SelectedComponentRepositoryImpl
import com.android.systemui.controls.settings.ControlsSettingsDialogManager
import com.android.systemui.controls.settings.ControlsSettingsDialogManagerImpl
import com.android.systemui.controls.ui.ControlActionCoordinator
@@ -114,6 +116,11 @@
repository: AuthorizedPanelsRepositoryImpl
): AuthorizedPanelsRepository
+ @Binds
+ abstract fun providePreferredPanelRepository(
+ repository: SelectedComponentRepositoryImpl
+ ): SelectedComponentRepository
+
@BindsOptionalOf
abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
index e51e832..5c2402b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
@@ -20,6 +20,8 @@
import android.content.Context
import android.content.SharedPreferences
import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
@@ -30,7 +32,8 @@
constructor(
private val context: Context,
private val userFileManager: UserFileManager,
- private val userTracker: UserTracker
+ private val userTracker: UserTracker,
+ private val featureFlags: FeatureFlags,
) : AuthorizedPanelsRepository {
override fun getAuthorizedPanels(): Set<String> {
@@ -71,8 +74,18 @@
userTracker.userId,
)
- // If we've never run this (i.e., the key doesn't exist), add the default packages
- if (sharedPref.getStringSet(KEY, null) == null) {
+ // We should add default packages in two cases:
+ // 1) We've never run this
+ // 2) APP_PANELS_REMOVE_APPS_ALLOWED got disabled after user removed all apps
+ val needToSetup =
+ if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
+ sharedPref.getStringSet(KEY, null) == null
+ } else {
+ // There might be an empty set that need to be overridden after the feature has been
+ // turned off after being turned on
+ sharedPref.getStringSet(KEY, null).isNullOrEmpty()
+ }
+ if (needToSetup) {
sharedPref.edit().putStringSet(KEY, getPreferredPackages()).apply()
}
return sharedPref
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt
new file mode 100644
index 0000000..5bb6eec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 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.controls.panels
+
+import android.content.ComponentName
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.ui.SelectedItem
+import com.android.systemui.flags.Flags
+
+/** Stores user-selected preferred component. */
+interface SelectedComponentRepository {
+
+ /**
+ * Returns currently set preferred component, or null when nothing is set. Consider using
+ * [ControlsUiController.getPreferredSelectedItem] to get domain specific data
+ */
+ fun getSelectedComponent(): SelectedComponent?
+
+ /** Sets preferred component. Use [getSelectedComponent] to get current one */
+ fun setSelectedComponent(selectedComponent: SelectedComponent)
+
+ /** Clears current preferred component. [getSelectedComponent] will return null afterwards */
+ fun removeSelectedComponent()
+
+ /**
+ * Return true when default preferred component should be set up and false the otherwise. This
+ * is always true when [Flags.APP_PANELS_REMOVE_APPS_ALLOWED] is disabled
+ */
+ fun shouldAddDefaultComponent(): Boolean
+
+ /**
+ * Sets if default component should be added. This is ignored when
+ * [Flags.APP_PANELS_REMOVE_APPS_ALLOWED] is disabled
+ */
+ fun setShouldAddDefaultComponent(shouldAdd: Boolean)
+
+ data class SelectedComponent(
+ val name: String,
+ val componentName: ComponentName?,
+ val isPanel: Boolean,
+ ) {
+ constructor(
+ selectedItem: SelectedItem
+ ) : this(
+ name = selectedItem.name.toString(),
+ componentName = selectedItem.componentName,
+ isPanel = selectedItem is SelectedItem.PanelItem,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
new file mode 100644
index 0000000..0fb5b66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 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.controls.panels
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.SharedPreferences
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import javax.inject.Inject
+
+@SysUISingleton
+class SelectedComponentRepositoryImpl
+@Inject
+constructor(
+ private val userFileManager: UserFileManager,
+ private val userTracker: UserTracker,
+ private val featureFlags: FeatureFlags,
+) : SelectedComponentRepository {
+
+ private companion object {
+ const val PREF_COMPONENT = "controls_component"
+ const val PREF_STRUCTURE_OR_APP_NAME = "controls_structure"
+ const val PREF_IS_PANEL = "controls_is_panel"
+ const val SHOULD_ADD_DEFAULT_PANEL = "should_add_default_panel"
+ }
+
+ private val sharedPreferences: SharedPreferences
+ get() =
+ userFileManager.getSharedPreferences(
+ fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+ mode = Context.MODE_PRIVATE,
+ userId = userTracker.userId
+ )
+
+ override fun getSelectedComponent(): SelectedComponentRepository.SelectedComponent? {
+ with(sharedPreferences) {
+ val componentString = getString(PREF_COMPONENT, null) ?: return null
+ return SelectedComponentRepository.SelectedComponent(
+ name = getString(PREF_STRUCTURE_OR_APP_NAME, "")!!,
+ componentName = ComponentName.unflattenFromString(componentString),
+ isPanel = getBoolean(PREF_IS_PANEL, false)
+ )
+ }
+ }
+
+ override fun setSelectedComponent(
+ selectedComponent: SelectedComponentRepository.SelectedComponent
+ ) {
+ sharedPreferences
+ .edit()
+ .putString(PREF_COMPONENT, selectedComponent.componentName?.flattenToString())
+ .putString(PREF_STRUCTURE_OR_APP_NAME, selectedComponent.name)
+ .putBoolean(PREF_IS_PANEL, selectedComponent.isPanel)
+ .apply()
+ }
+
+ override fun removeSelectedComponent() {
+ sharedPreferences
+ .edit()
+ .remove(PREF_COMPONENT)
+ .remove(PREF_STRUCTURE_OR_APP_NAME)
+ .remove(PREF_IS_PANEL)
+ .apply()
+ }
+
+ override fun shouldAddDefaultComponent(): Boolean =
+ if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
+ sharedPreferences.getBoolean(SHOULD_ADD_DEFAULT_PANEL, true)
+ } else {
+ true
+ }
+
+ override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
+ sharedPreferences.edit().putBoolean(SHOULD_ADD_DEFAULT_PANEL, shouldAdd).apply()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
index 9d99253..3a4a00c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
@@ -18,17 +18,16 @@
package com.android.systemui.controls.start
import android.content.Context
-import android.content.res.Resources
import android.os.UserHandle
import com.android.systemui.CoreStartable
-import com.android.systemui.R
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.UserTracker
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -37,7 +36,7 @@
* Started with SystemUI to perform early operations for device controls subsystem (only if enabled)
*
* In particular, it will perform the following:
- * * If there is no preferred selection for provider and at least one of the preferred packages
+ * * If there is no preferred selection for provider and at least one of the preferred packages
* provides a panel, it will select the first one that does.
* * If the preferred selection provides a panel, it will bind to that service (to reduce latency on
* displaying the panel).
@@ -48,10 +47,11 @@
class ControlsStartable
@Inject
constructor(
- @Main private val resources: Resources,
- @Background private val executor: Executor,
- private val controlsComponent: ControlsComponent,
- private val userTracker: UserTracker
+ @Background private val executor: Executor,
+ private val controlsComponent: ControlsComponent,
+ private val userTracker: UserTracker,
+ private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+ private val selectedComponentRepository: SelectedComponentRepository,
) : CoreStartable {
// These two controllers can only be accessed after `start` method once we've checked if the
@@ -85,12 +85,15 @@
}
private fun selectDefaultPanelIfNecessary() {
+ if (!selectedComponentRepository.shouldAddDefaultComponent()) {
+ return
+ }
val currentSelection = controlsController.getPreferredSelection()
if (currentSelection == SelectedItem.EMPTY_SELECTION) {
val availableServices = controlsListingController.getCurrentServices()
val panels = availableServices.filter { it.panelActivity != null }
- resources
- .getStringArray(R.array.config_controlsPreferredPackages)
+ authorizedPanelsRepository
+ .getPreferredPackages()
// Looking for the first element in the string array such that there is one package
// that has a panel. It will return null if there are no packages in the array,
// or if no packages in the array have a panel associated with it.
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index 58673bb..0d53117 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -64,8 +64,6 @@
* This element will be the one that appears when the user first opens the controls activity.
*/
fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem
-
- fun updatePreferences(selectedItem: SelectedItem)
}
sealed class SelectedItem {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index c61dad6..5da86de9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -64,6 +64,7 @@
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -73,9 +74,7 @@
import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsPopupMenu
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.asIndenting
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -84,7 +83,7 @@
import dagger.Lazy
import java.io.PrintWriter
import java.text.Collator
-import java.util.*
+import java.util.Optional
import java.util.function.Consumer
import javax.inject.Inject
@@ -98,25 +97,22 @@
@Main val uiExecutor: DelayableExecutor,
@Background val bgExecutor: DelayableExecutor,
val controlsListingController: Lazy<ControlsListingController>,
- val controlActionCoordinator: ControlActionCoordinator,
+ private val controlActionCoordinator: ControlActionCoordinator,
private val activityStarter: ActivityStarter,
private val iconCache: CustomIconCache,
private val controlsMetricsLogger: ControlsMetricsLogger,
private val keyguardStateController: KeyguardStateController,
- private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
private val taskViewFactory: Optional<TaskViewFactory>,
private val controlsSettingsRepository: ControlsSettingsRepository,
private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+ private val selectedComponentRepository: SelectedComponentRepository,
private val featureFlags: FeatureFlags,
private val dialogsFactory: ControlsDialogsFactory,
dumpManager: DumpManager
) : ControlsUiController, Dumpable {
companion object {
- private const val PREF_COMPONENT = "controls_component"
- private const val PREF_STRUCTURE_OR_APP_NAME = "controls_structure"
- private const val PREF_IS_PANEL = "controls_is_panel"
private const val FADE_IN_MILLIS = 200L
@@ -138,12 +134,6 @@
private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow)
private var retainCache = false
private var lastSelections = emptyList<SelectionItem>()
- private val sharedPreferences
- get() = userFileManager.getSharedPreferences(
- fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
- mode = 0,
- userId = userTracker.userId
- )
private var taskViewController: PanelTaskViewController? = null
@@ -341,20 +331,18 @@
if (!controlsController.get().removeFavorites(componentName)) {
return@createRemoveAppDialog
}
- if (
- sharedPreferences.getString(PREF_COMPONENT, "") ==
- componentName.flattenToString()
- ) {
- sharedPreferences
- .edit()
- .remove(PREF_COMPONENT)
- .remove(PREF_STRUCTURE_OR_APP_NAME)
- .remove(PREF_IS_PANEL)
- .commit()
+
+ if (selectedComponentRepository.getSelectedComponent()?.componentName ==
+ componentName) {
+ selectedComponentRepository.removeSelectedComponent()
}
- allStructures = controlsController.get().getFavorites()
- selectedItem = getPreferredSelectedItem(allStructures)
+ val selectedItem = getPreferredSelectedItem(controlsController.get().getFavorites())
+ if (selectedItem == SelectedItem.EMPTY_SELECTION) {
+ // User removed the last panel. In this case we start app selection flow and don't
+ // want to auto-add it again
+ selectedComponentRepository.setShouldAddDefaultComponent(false)
+ }
reload(parent)
}.apply { show() }
}
@@ -522,8 +510,7 @@
ADD_APP_ID
))
}
- if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED) &&
- controlsController.get().canRemoveFavorites(selectedItem.componentName)) {
+ if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
add(OverflowMenuAdapter.MenuItem(
context.getText(R.string.controls_menu_remove),
REMOVE_APP_ID,
@@ -569,7 +556,7 @@
ADD_CONTROLS_ID -> startFavoritingActivity(selectedStructure)
EDIT_CONTROLS_ID -> startEditingActivity(selectedStructure)
REMOVE_APP_ID -> startRemovingApp(
- selectedStructure.componentName, selectionItem.appName
+ selectionItem.componentName, selectionItem.appName
)
}
dismiss()
@@ -714,29 +701,22 @@
}
override fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem {
- val sp = sharedPreferences
-
- val component = sp.getString(PREF_COMPONENT, null)?.let {
- ComponentName.unflattenFromString(it)
- } ?: EMPTY_COMPONENT
- val name = sp.getString(PREF_STRUCTURE_OR_APP_NAME, "")!!
- val isPanel = sp.getBoolean(PREF_IS_PANEL, false)
- return if (isPanel) {
- SelectedItem.PanelItem(name, component)
+ val preferredPanel = selectedComponentRepository.getSelectedComponent()
+ val component = preferredPanel?.componentName ?: EMPTY_COMPONENT
+ return if (preferredPanel?.isPanel == true) {
+ SelectedItem.PanelItem(preferredPanel.name, component)
} else {
if (structures.isEmpty()) return SelectedItem.EMPTY_SELECTION
SelectedItem.StructureItem(structures.firstOrNull {
- component == it.componentName && name == it.structure
- } ?: structures.get(0))
+ component == it.componentName && preferredPanel?.name == it.structure
+ } ?: structures[0])
}
}
- override fun updatePreferences(selectedItem: SelectedItem) {
- sharedPreferences.edit()
- .putString(PREF_COMPONENT, selectedItem.componentName.flattenToString())
- .putString(PREF_STRUCTURE_OR_APP_NAME, selectedItem.name.toString())
- .putBoolean(PREF_IS_PANEL, selectedItem is SelectedItem.PanelItem)
- .apply()
+ private fun updatePreferences(selectedItem: SelectedItem) {
+ selectedComponentRepository.setSelectedComponent(
+ SelectedComponentRepository.SelectedComponent(selectedItem)
+ )
}
private fun maybeUpdateSelectedItem(item: SelectionItem): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 7a1abf4..63a4fd2 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -70,6 +70,8 @@
import com.android.systemui.security.data.repository.SecurityRepositoryModule;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolatorImpl;
import com.android.systemui.smartspace.dagger.SmartspaceModule;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -309,4 +311,8 @@
@Binds
abstract FgsManagerController bindFgsManagerController(FgsManagerControllerImpl impl);
+
+ @Binds
+ abstract LargeScreenShadeInterpolator largeScreensShadeInterpolator(
+ LargeScreenShadeInterpolatorImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index ca1cef3..d0a92f0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -43,7 +43,6 @@
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch
@@ -131,9 +130,17 @@
}
}
- /** Starts the dream content and dream overlay entry animations. */
+ /**
+ * Starts the dream content and dream overlay entry animations.
+ *
+ * @param downwards if true, the entry animation translations downwards into position rather
+ * than upwards.
+ */
@JvmOverloads
- fun startEntryAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
+ fun startEntryAnimations(
+ downwards: Boolean,
+ animatorBuilder: () -> AnimatorSet = { AnimatorSet() }
+ ) {
cancelAnimations()
mAnimator =
@@ -153,7 +160,7 @@
interpolator = Interpolators.LINEAR
),
translationYAnimator(
- from = mDreamInTranslationYDistance.toFloat(),
+ from = mDreamInTranslationYDistance.toFloat() * (if (downwards) -1 else 1),
to = 0f,
durationMs = mDreamInTranslationYDurationMs,
interpolator = Interpolators.EMPHASIZED_DECELERATE
@@ -167,6 +174,71 @@
}
}
+ /**
+ * Starts the dream content and dream overlay exit animations.
+ *
+ * This should only be used when the low light dream is entering, animations to/from other SysUI
+ * views is controlled by `transitionViewModel`.
+ */
+ // TODO(b/256916668): integrate with the keyguard transition model once dream surfaces work is
+ // done.
+ @JvmOverloads
+ fun startExitAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }): Animator {
+ cancelAnimations()
+
+ mAnimator =
+ animatorBuilder().apply {
+ playTogether(
+ translationYAnimator(
+ from = 0f,
+ to = -mDreamInTranslationYDistance.toFloat(),
+ durationMs = mDreamInTranslationYDurationMs,
+ delayMs = 0,
+ interpolator = Interpolators.EMPHASIZED
+ ),
+ alphaAnimator(
+ from =
+ mCurrentAlphaAtPosition.getOrDefault(
+ key = POSITION_BOTTOM,
+ defaultValue = 1f
+ ),
+ to = 0f,
+ durationMs = mDreamInComplicationsAnimDurationMs,
+ delayMs = 0,
+ positions = POSITION_BOTTOM
+ )
+ .apply {
+ doOnEnd {
+ // The logical end of the animation is once the alpha and blur
+ // animations finish, end the animation so that any listeners are
+ // notified. The Y translation animation is much longer than all of
+ // the other animations due to how the spec is defined, but is not
+ // expected to run to completion.
+ mAnimator?.end()
+ }
+ },
+ alphaAnimator(
+ from =
+ mCurrentAlphaAtPosition.getOrDefault(
+ key = POSITION_TOP,
+ defaultValue = 1f
+ ),
+ to = 0f,
+ durationMs = mDreamInComplicationsAnimDurationMs,
+ delayMs = 0,
+ positions = POSITION_TOP
+ )
+ )
+ doOnEnd {
+ mAnimator = null
+ mOverlayStateController.setExitAnimationsRunning(false)
+ }
+ start()
+ }
+ mOverlayStateController.setExitAnimationsRunning(true)
+ return mAnimator as AnimatorSet
+ }
+
/** Starts the dream content and dream overlay exit animations. */
fun wakeUp(doneCallback: Runnable, executor: DelayableExecutor) {
cancelAnimations()
@@ -182,19 +254,6 @@
}
}
- /**
- * Ends the dream content and dream overlay animations, if they're currently running.
- *
- * @see [AnimatorSet.end]
- */
- fun endAnimations() {
- mAnimator =
- mAnimator?.let {
- it.end()
- null
- }
- }
-
private fun blurAnimator(
view: View,
fromBlurRadius: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 50cfb6a..4b478cd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -23,6 +23,7 @@
import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_BOTTOM;
import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_TOP;
+import android.animation.Animator;
import android.content.res.Resources;
import android.os.Handler;
import android.util.MathUtils;
@@ -31,6 +32,7 @@
import androidx.annotation.NonNull;
+import com.android.dream.lowlight.LowLightTransitionCoordinator;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Main;
@@ -54,11 +56,14 @@
* View controller for {@link DreamOverlayContainerView}.
*/
@DreamOverlayComponent.DreamOverlayScope
-public class DreamOverlayContainerViewController extends ViewController<DreamOverlayContainerView> {
+public class DreamOverlayContainerViewController extends
+ ViewController<DreamOverlayContainerView> implements
+ LowLightTransitionCoordinator.LowLightEnterListener {
private final DreamOverlayStatusBarViewController mStatusBarViewController;
private final BlurUtils mBlurUtils;
private final DreamOverlayAnimationsController mDreamOverlayAnimationsController;
private final DreamOverlayStateController mStateController;
+ private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;
private final ComplicationHostViewController mComplicationHostViewController;
@@ -143,19 +148,18 @@
};
/**
- * If true, overlay entry animations should be skipped once.
- *
- * This is turned on when exiting low light and should be turned off once the entry animations
- * are skipped once.
+ * If {@code true}, the dream has just transitioned from the low light dream back to the user
+ * dream and we should play an entry animation where the overlay slides in downwards from the
+ * top instead of the typicla slide in upwards from the bottom.
*/
- private boolean mSkipEntryAnimations;
+ private boolean mExitingLowLight;
private final DreamOverlayStateController.Callback
mDreamOverlayStateCallback =
new DreamOverlayStateController.Callback() {
@Override
public void onExitLowLight() {
- mSkipEntryAnimations = true;
+ mExitingLowLight = true;
}
};
@@ -165,6 +169,7 @@
ComplicationHostViewController complicationHostViewController,
@Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
DreamOverlayStatusBarViewController statusBarViewController,
+ LowLightTransitionCoordinator lowLightTransitionCoordinator,
BlurUtils blurUtils,
@Main Handler handler,
@Main Resources resources,
@@ -182,6 +187,7 @@
mBlurUtils = blurUtils;
mDreamOverlayAnimationsController = animationsController;
mStateController = stateController;
+ mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
mBouncerlessScrimController = bouncerlessScrimController;
mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback);
@@ -208,6 +214,7 @@
mStatusBarViewController.init();
mComplicationHostViewController.init();
mDreamOverlayAnimationsController.init(mView);
+ mLowLightTransitionCoordinator.setLowLightEnterListener(this);
}
@Override
@@ -219,14 +226,10 @@
// Start dream entry animations. Skip animations for low light clock.
if (!mStateController.isLowLightActive()) {
- mDreamOverlayAnimationsController.startEntryAnimations();
-
- if (mSkipEntryAnimations) {
- // If we're transitioning from the low light dream back to the user dream, skip the
- // overlay animations and show immediately.
- mDreamOverlayAnimationsController.endAnimations();
- mSkipEntryAnimations = false;
- }
+ // If this is transitioning from the low light dream to the user dream, the overlay
+ // should translate in downwards instead of upwards.
+ mDreamOverlayAnimationsController.startEntryAnimations(mExitingLowLight);
+ mExitingLowLight = false;
}
}
@@ -310,4 +313,12 @@
mDreamOverlayAnimationsController.wakeUp(onAnimationEnd, callbackExecutor);
}
+
+ @Override
+ public Animator onBeforeEnterLowLight() {
+ // Return the animator so that the transition coordinator waits for the overlay exit
+ // animations to finish before entering low light, as otherwise the default DreamActivity
+ // animation plays immediately and there's no time for this animation to play.
+ return mDreamOverlayAnimationsController.startExitAnimations();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
index a2e11b2..24e90f0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -22,14 +22,18 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Debug;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Log;
import android.view.View;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.lifecycle.LifecycleOwner;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.util.ViewController;
+import com.android.systemui.util.settings.SecureSettings;
import java.util.Collection;
import java.util.HashMap;
@@ -54,6 +58,8 @@
private final LifecycleOwner mLifecycleOwner;
private final ComplicationCollectionViewModel mComplicationCollectionViewModel;
private final HashMap<ComplicationId, Complication.ViewHolder> mComplications = new HashMap<>();
+ @VisibleForTesting
+ boolean mIsAnimationEnabled;
// Whether dream entry animations are finished.
private boolean mEntryAnimationsFinished = false;
@@ -64,7 +70,8 @@
ComplicationLayoutEngine layoutEngine,
DreamOverlayStateController dreamOverlayStateController,
LifecycleOwner lifecycleOwner,
- @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel) {
+ @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel,
+ SecureSettings secureSettings) {
super(view);
mLayoutEngine = layoutEngine;
mLifecycleOwner = lifecycleOwner;
@@ -78,6 +85,10 @@
mDreamOverlayStateController.areEntryAnimationsFinished();
}
});
+
+ // Whether animations are enabled.
+ mIsAnimationEnabled = secureSettings.getFloatForUser(
+ Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f, UserHandle.USER_CURRENT) != 0.0f;
}
@Override
@@ -148,7 +159,7 @@
// Complications to be added before dream entry animations are finished are set
// to invisible and are animated in.
- if (!mEntryAnimationsFinished) {
+ if (!mEntryAnimationsFinished && mIsAnimationEnabled) {
view.setVisibility(View.INVISIBLE);
}
mComplications.put(id, viewHolder);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
new file mode 100644
index 0000000..b20e33a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 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.flags
+
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import javax.inject.Named
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+/** Restarts the process after all passed in [Condition]s are true. */
+class ConditionalRestarter
+@Inject
+constructor(
+ private val systemExitRestarter: SystemExitRestarter,
+ private val conditions: Set<@JvmSuppressWildcards Condition>,
+ @Named(RESTART_DELAY) private val restartDelaySec: Long,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : Restarter {
+
+ private var restartJob: Job? = null
+ private var pendingReason = ""
+ private var androidRestartRequested = false
+
+ override fun restartSystemUI(reason: String) {
+ Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting when idle.")
+ scheduleRestart(reason)
+ }
+
+ override fun restartAndroid(reason: String) {
+ Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting when idle.")
+ androidRestartRequested = true
+ scheduleRestart(reason)
+ }
+
+ private fun scheduleRestart(reason: String = "") {
+ pendingReason = if (reason.isEmpty()) pendingReason else reason
+
+ if (conditions.all { c -> c.canRestartNow(this::scheduleRestart) }) {
+ if (restartJob == null) {
+ restartJob =
+ applicationScope.launch(backgroundDispatcher) {
+ delay(TimeUnit.SECONDS.toMillis(restartDelaySec))
+ restartNow()
+ }
+ }
+ } else {
+ restartJob?.cancel()
+ restartJob = null
+ }
+ }
+
+ private fun restartNow() {
+ if (androidRestartRequested) {
+ systemExitRestarter.restartAndroid(pendingReason)
+ } else {
+ systemExitRestarter.restartSystemUI(pendingReason)
+ }
+ }
+
+ interface Condition {
+ /**
+ * Should return true if the system is ready to restart.
+ *
+ * A call to this function means that we want to restart and are waiting for this condition
+ * to return true.
+ *
+ * retryFn should be cached if it is _not_ ready to restart, and later called when it _is_
+ * ready to restart. At that point, this method will be called again to verify that the
+ * system is ready.
+ *
+ * Multiple calls to an instance of this method may happen for a single restart attempt if
+ * multiple [Condition]s are being checked. If any one [Condition] returns false, all the
+ * [Condition]s will need to be rechecked on the next restart attempt.
+ */
+ fun canRestartNow(retryFn: () -> Unit): Boolean
+ }
+
+ companion object {
+ const val RESTART_DELAY = "restarter_restart_delay"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
deleted file mode 100644
index a6956a4..0000000
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
+++ /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.flags
-
-import android.util.Log
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import javax.inject.Inject
-
-/** Restarts SystemUI when the screen is locked. */
-class FeatureFlagsDebugRestarter
-@Inject
-constructor(
- private val wakefulnessLifecycle: WakefulnessLifecycle,
- private val systemExitRestarter: SystemExitRestarter,
-) : Restarter {
-
- private var androidRestartRequested = false
- private var pendingReason = ""
-
- val observer =
- object : WakefulnessLifecycle.Observer {
- override fun onFinishedGoingToSleep() {
- Log.d(FeatureFlagsDebug.TAG, "Restarting due to systemui flag change")
- restartNow()
- }
- }
-
- override fun restartSystemUI(reason: String) {
- Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting on next screen off.")
- Log.i(FeatureFlagsDebug.TAG, reason)
- scheduleRestart(reason)
- }
-
- override fun restartAndroid(reason: String) {
- Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting on next screen off.")
- androidRestartRequested = true
- scheduleRestart(reason)
- }
-
- fun scheduleRestart(reason: String) {
- pendingReason = reason
- if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) {
- restartNow()
- } else {
- wakefulnessLifecycle.addObserver(observer)
- }
- }
-
- private fun restartNow() {
- if (androidRestartRequested) {
- systemExitRestarter.restartAndroid(pendingReason)
- } else {
- systemExitRestarter.restartSystemUI(pendingReason)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
deleted file mode 100644
index c08266c..0000000
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
+++ /dev/null
@@ -1,101 +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.flags
-
-import android.util.Log
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
-import com.android.systemui.statusbar.policy.BatteryController
-import com.android.systemui.util.concurrency.DelayableExecutor
-import java.util.concurrent.TimeUnit
-import javax.inject.Inject
-
-/** Restarts SystemUI when the device appears idle. */
-class FeatureFlagsReleaseRestarter
-@Inject
-constructor(
- private val wakefulnessLifecycle: WakefulnessLifecycle,
- private val batteryController: BatteryController,
- @Background private val bgExecutor: DelayableExecutor,
- private val systemExitRestarter: SystemExitRestarter
-) : Restarter {
- var listenersAdded = false
- var pendingRestart: Runnable? = null
- private var pendingReason = ""
- var androidRestartRequested = false
-
- val observer =
- object : WakefulnessLifecycle.Observer {
- override fun onFinishedGoingToSleep() {
- scheduleRestart(pendingReason)
- }
- }
-
- val batteryCallback =
- object : BatteryController.BatteryStateChangeCallback {
- override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
- scheduleRestart(pendingReason)
- }
- }
-
- override fun restartSystemUI(reason: String) {
- Log.d(
- FeatureFlagsDebug.TAG,
- "SystemUI Restart requested. Restarting when plugged in and idle."
- )
- scheduleRestart(reason)
- }
-
- override fun restartAndroid(reason: String) {
- Log.d(
- FeatureFlagsDebug.TAG,
- "Android Restart requested. Restarting when plugged in and idle."
- )
- androidRestartRequested = true
- scheduleRestart(reason)
- }
-
- private fun scheduleRestart(reason: String) {
- // Don't bother adding listeners twice.
- pendingReason = reason
- if (!listenersAdded) {
- listenersAdded = true
- wakefulnessLifecycle.addObserver(observer)
- batteryController.addCallback(batteryCallback)
- }
- if (
- wakefulnessLifecycle.wakefulness == WAKEFULNESS_ASLEEP && batteryController.isPluggedIn
- ) {
- if (pendingRestart == null) {
- pendingRestart = bgExecutor.executeDelayed(this::restartNow, 30L, TimeUnit.SECONDS)
- }
- } else if (pendingRestart != null) {
- pendingRestart?.run()
- pendingRestart = null
- }
- }
-
- private fun restartNow() {
- Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change")
- if (androidRestartRequested) {
- systemExitRestarter.restartAndroid(pendingReason)
- } else {
- systemExitRestarter.restartSystemUI(pendingReason)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 95b37ac..6209c0b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -71,8 +71,12 @@
val NOTIFICATION_MEMORY_MONITOR_ENABLED =
releasedFlag(112, "notification_memory_monitor_enabled")
+ /**
+ * This flag is server-controlled and should stay as [unreleasedFlag] since we never want to
+ * enable it on release builds.
+ */
val NOTIFICATION_MEMORY_LOGGING_ENABLED =
- unreleasedFlag(119, "notification_memory_logging_enabled", teamfood = true)
+ unreleasedFlag(119, "notification_memory_logging_enabled")
// TODO(b/254512731): Tracking Bug
@JvmField val NOTIFICATION_DISMISSAL_FADE = releasedFlag(113, "notification_dismissal_fade")
@@ -104,6 +108,13 @@
val NOTIFICATION_ANIMATE_BIG_PICTURE =
releasedFlag(120, "notification_animate_big_picture", teamfood = true)
+ @JvmField
+ val ANIMATED_NOTIFICATION_SHADE_INSETS =
+ unreleasedFlag(270682168, "animated_notification_shade_insets", teamfood = true)
+
+ // TODO(b/268005230): Tracking Bug
+ @JvmField val SENSITIVE_REVEAL_ANIM = unreleasedFlag(268005230, "sensitive_reveal_anim")
+
// 200 - keyguard/lockscreen
// ** Flag retired **
// public static final BooleanFlag KEYGUARD_LAYOUT =
@@ -377,13 +388,16 @@
@JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
// TODO(b/270882464): Tracking Bug
- val ENABLE_DOCK_SETUP_V2 = unreleasedFlag(1005, "enable_dock_setup_v2")
+ val ENABLE_DOCK_SETUP_V2 = unreleasedFlag(1005, "enable_dock_setup_v2", teamfood = true)
// TODO(b/265045965): Tracking Bug
val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
@JvmField
- val ENABLE_LOW_LIGHT_CLOCK_UNDOCKED = unreleasedFlag(1004, "enable_low_light_clock_undocked")
+ // TODO(b/271428141): Tracking Bug
+ val ENABLE_LOW_LIGHT_CLOCK_UNDOCKED = unreleasedFlag(
+ 1004,
+ "enable_low_light_clock_undocked", teamfood = true)
// 1100 - windowing
@Keep
@@ -469,7 +483,14 @@
@Keep
@JvmField
val ENABLE_PIP_APP_ICON_OVERLAY =
- sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = false)
+ sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = true)
+
+ // TODO(b/272110828): Tracking bug
+ @Keep
+ @JvmField
+ val ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
+ sysPropBooleanFlag(
+ 1116, "persist.wm.debug.enable_move_floating_window_in_tabletop", default = false)
// 1200 - predictive back
@Keep
@@ -571,7 +592,7 @@
@JvmField
val LEAVE_SHADE_OPEN_FOR_BUGREPORT = releasedFlag(1800, "leave_shade_open_for_bugreport")
// TODO(b/265944639): Tracking Bug
- @JvmField val DUAL_SHADE = releasedFlag(1801, "dual_shade")
+ @JvmField val DUAL_SHADE = unreleasedFlag(1801, "dual_shade")
// 1900
@JvmField val NOTE_TASKS = releasedFlag(1900, "keycode_flag")
@@ -645,4 +666,9 @@
// TODO(b/259428678): Tracking Bug
@JvmField
val KEYBOARD_BACKLIGHT_INDICATOR = unreleasedFlag(2601, "keyboard_backlight_indicator")
+
+ // TODO(b/272036292): Tracking Bug
+ @JvmField
+ val LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION =
+ unreleasedFlag(2602, "large_shade_granular_alpha_interpolation")
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
index 0054d26..3c50125 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.flags
+import dagger.Binds
import dagger.Module
import dagger.Provides
import javax.inject.Named
@@ -22,6 +23,8 @@
/** Module containing shared code for all FeatureFlag implementations. */
@Module
interface FlagsCommonModule {
+ @Binds fun bindsRestarter(impl: ConditionalRestarter): Restarter
+
companion object {
const val ALL_FLAGS = "all_flags"
diff --git a/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
new file mode 100644
index 0000000..3120638
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 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.flags
+
+import com.android.systemui.statusbar.policy.BatteryController
+import javax.inject.Inject
+
+/** Returns true when the device is plugged in. */
+class PluggedInCondition
+@Inject
+constructor(
+ private val batteryController: BatteryController,
+) : ConditionalRestarter.Condition {
+
+ var listenersAdded = false
+ var retryFn: (() -> Unit)? = null
+
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+ retryFn?.invoke()
+ }
+ }
+
+ override fun canRestartNow(retryFn: () -> Unit): Boolean {
+ if (!listenersAdded) {
+ listenersAdded = true
+ batteryController.addCallback(batteryCallback)
+ }
+
+ this.retryFn = retryFn
+
+ return batteryController.isPluggedIn
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
new file mode 100644
index 0000000..49e61af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 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.flags
+
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import javax.inject.Inject
+
+/** Returns true when the device is "asleep" as defined by the [WakefullnessLifecycle]. */
+class ScreenIdleCondition
+@Inject
+constructor(
+ private val wakefulnessLifecycle: WakefulnessLifecycle,
+) : ConditionalRestarter.Condition {
+
+ var listenersAdded = false
+ var retryFn: (() -> Unit)? = null
+
+ val observer =
+ object : WakefulnessLifecycle.Observer {
+ override fun onFinishedGoingToSleep() {
+ retryFn?.invoke()
+ }
+ }
+
+ override fun canRestartNow(retryFn: () -> Unit): Boolean {
+ if (!listenersAdded) {
+ listenersAdded = true
+ wakefulnessLifecycle.addObserver(observer)
+ }
+
+ this.retryFn = retryFn
+
+ return wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index e43f83b..07753ca 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -788,6 +788,11 @@
@Override
public boolean onLongPress() {
+ // don't actually trigger the reboot if we are running stability
+ // tests via monkey
+ if (ActivityManager.isUserAMonkey()) {
+ return false;
+ }
mUiEventLogger.log(GlobalActionsEvent.GA_SHUTDOWN_LONG_PRESS);
if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
mWindowManagerFuncs.reboot(true);
@@ -808,6 +813,11 @@
@Override
public void onPress() {
+ // don't actually trigger the shutdown if we are running stability
+ // tests via monkey
+ if (ActivityManager.isUserAMonkey()) {
+ return;
+ }
mUiEventLogger.log(GlobalActionsEvent.GA_SHUTDOWN_PRESS);
// shutdown by making sure radio and power are handled accordingly.
mWindowManagerFuncs.shutdown();
@@ -919,6 +929,11 @@
@Override
public boolean onLongPress() {
+ // don't actually trigger the reboot if we are running stability
+ // tests via monkey
+ if (ActivityManager.isUserAMonkey()) {
+ return false;
+ }
mUiEventLogger.log(GlobalActionsEvent.GA_REBOOT_LONG_PRESS);
if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
mWindowManagerFuncs.reboot(true);
@@ -939,6 +954,11 @@
@Override
public void onPress() {
+ // don't actually trigger the reboot if we are running stability
+ // tests via monkey
+ if (ActivityManager.isUserAMonkey()) {
+ return;
+ }
mUiEventLogger.log(GlobalActionsEvent.GA_REBOOT_PRESS);
mWindowManagerFuncs.reboot(false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt
index 85d0379..5e806b6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt
@@ -46,8 +46,15 @@
viewModel.dialogContent.collect { dialogViewModel ->
if (dialogViewModel != null) {
if (dialog == null) {
- dialog = KeyboardBacklightDialog(context, dialogViewModel)
- // pass viewModel and show
+ dialog =
+ KeyboardBacklightDialog(
+ context,
+ initialCurrentLevel = dialogViewModel.currentValue,
+ initialMaxLevel = dialogViewModel.maxValue
+ )
+ dialog?.show()
+ } else {
+ dialog?.updateState(dialogViewModel.currentValue, dialogViewModel.maxValue)
}
} else {
dialog?.dismiss()
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index b68a2a8..a173f8b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -17,16 +17,260 @@
package com.android.systemui.keyboard.backlight.ui.view
+import android.annotation.ColorInt
import android.app.Dialog
import android.content.Context
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RoundRectShape
import android.os.Bundle
-import com.android.systemui.keyboard.backlight.ui.viewmodel.BacklightDialogContentViewModel
+import android.view.Gravity
+import android.view.Window
+import android.view.WindowManager
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.LinearLayout.LayoutParams
+import android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
+import com.android.systemui.R
+import com.android.systemui.util.children
-class KeyboardBacklightDialog(context: Context, val viewModel: BacklightDialogContentViewModel) :
- Dialog(context) {
+class KeyboardBacklightDialog(
+ context: Context,
+ initialCurrentLevel: Int,
+ initialMaxLevel: Int,
+) : Dialog(context) {
+
+ private data class RootProperties(
+ val cornerRadius: Float,
+ val verticalPadding: Int,
+ val horizontalPadding: Int,
+ )
+
+ private data class BacklightIconProperties(
+ val width: Int,
+ val height: Int,
+ val leftMargin: Int,
+ )
+
+ private data class StepViewProperties(
+ val width: Int,
+ val height: Int,
+ val horizontalMargin: Int,
+ val smallRadius: Float,
+ val largeRadius: Float,
+ )
+
+ private var currentLevel: Int = 0
+ private var maxLevel: Int = 0
+
+ private lateinit var rootView: LinearLayout
+
+ private var dialogBottomMargin = 208
+ private lateinit var rootProperties: RootProperties
+ private lateinit var iconProperties: BacklightIconProperties
+ private lateinit var stepProperties: StepViewProperties
+ @ColorInt var filledRectangleColor: Int = 0
+ @ColorInt var emptyRectangleColor: Int = 0
+ @ColorInt var backgroundColor: Int = 0
+
+ init {
+ currentLevel = initialCurrentLevel
+ maxLevel = initialMaxLevel
+ }
override fun onCreate(savedInstanceState: Bundle?) {
+ setUpWindowProperties(this)
+ setWindowTitle()
+ updateResources()
+ rootView = buildRootView()
+ setContentView(rootView)
super.onCreate(savedInstanceState)
- // TODO(b/268650355) Implement the dialog
+ updateState(currentLevel, maxLevel, forceRefresh = true)
+ }
+
+ private fun updateResources() {
+ context.resources.apply {
+ filledRectangleColor = getColor(R.color.backlight_indicator_step_filled)
+ emptyRectangleColor = getColor(R.color.backlight_indicator_step_empty)
+ backgroundColor = getColor(R.color.backlight_indicator_background)
+ rootProperties =
+ RootProperties(
+ cornerRadius =
+ getDimensionPixelSize(R.dimen.backlight_indicator_root_corner_radius)
+ .toFloat(),
+ verticalPadding =
+ getDimensionPixelSize(R.dimen.backlight_indicator_root_vertical_padding),
+ horizontalPadding =
+ getDimensionPixelSize(R.dimen.backlight_indicator_root_horizontal_padding)
+ )
+ iconProperties =
+ BacklightIconProperties(
+ width = getDimensionPixelSize(R.dimen.backlight_indicator_icon_width),
+ height = getDimensionPixelSize(R.dimen.backlight_indicator_icon_height),
+ leftMargin =
+ getDimensionPixelSize(R.dimen.backlight_indicator_icon_left_margin),
+ )
+ stepProperties =
+ StepViewProperties(
+ width = getDimensionPixelSize(R.dimen.backlight_indicator_step_width),
+ height = getDimensionPixelSize(R.dimen.backlight_indicator_step_height),
+ horizontalMargin =
+ getDimensionPixelSize(R.dimen.backlight_indicator_step_horizontal_margin),
+ smallRadius =
+ getDimensionPixelSize(R.dimen.backlight_indicator_step_small_radius)
+ .toFloat(),
+ largeRadius =
+ getDimensionPixelSize(R.dimen.backlight_indicator_step_large_radius)
+ .toFloat(),
+ )
+ }
+ }
+
+ fun updateState(current: Int, max: Int, forceRefresh: Boolean = false) {
+ if (maxLevel != max || forceRefresh) {
+ maxLevel = max
+ rootView.removeAllViews()
+ buildStepViews().forEach { rootView.addView(it) }
+ }
+ currentLevel = current
+ updateLevel()
+ }
+
+ private fun updateLevel() {
+ rootView.children.forEachIndexed(
+ action = { index, v ->
+ val drawable = v.background as ShapeDrawable
+ if (index <= currentLevel) {
+ updateColor(drawable, filledRectangleColor)
+ } else {
+ updateColor(drawable, emptyRectangleColor)
+ }
+ }
+ )
+ }
+
+ private fun updateColor(drawable: ShapeDrawable, @ColorInt color: Int) {
+ if (drawable.paint.color != color) {
+ drawable.paint.color = color
+ drawable.invalidateSelf()
+ }
+ }
+
+ private fun buildRootView(): LinearLayout {
+ val linearLayout =
+ LinearLayout(context).apply {
+ orientation = LinearLayout.HORIZONTAL
+ layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
+ setPadding(
+ /* left= */ rootProperties.horizontalPadding,
+ /* top= */ rootProperties.verticalPadding,
+ /* right= */ rootProperties.horizontalPadding,
+ /* bottom= */ rootProperties.verticalPadding
+ )
+ }
+ val drawable =
+ ShapeDrawable(
+ RoundRectShape(
+ /* outerRadii= */ FloatArray(8) { rootProperties.cornerRadius },
+ /* inset= */ null,
+ /* innerRadii= */ null
+ )
+ )
+ drawable.paint.color = backgroundColor
+ linearLayout.background = drawable
+ return linearLayout
+ }
+
+ private fun buildStepViews(): List<FrameLayout> {
+ val stepViews = (0..maxLevel).map { i -> createStepViewAt(i) }
+ stepViews[0].addView(createBacklightIconView())
+ return stepViews
+ }
+
+ private fun createStepViewAt(i: Int): FrameLayout {
+ return FrameLayout(context).apply {
+ layoutParams =
+ FrameLayout.LayoutParams(stepProperties.width, stepProperties.height).apply {
+ setMargins(
+ /* left= */ stepProperties.horizontalMargin,
+ /* top= */ 0,
+ /* right= */ stepProperties.horizontalMargin,
+ /* bottom= */ 0
+ )
+ }
+ val drawable =
+ ShapeDrawable(
+ RoundRectShape(
+ /* outerRadii= */ radiiForIndex(i, maxLevel),
+ /* inset= */ null,
+ /* innerRadii= */ null
+ )
+ )
+ drawable.paint.color = emptyRectangleColor
+ background = drawable
+ }
+ }
+
+ private fun createBacklightIconView(): ImageView {
+ return ImageView(context).apply {
+ setImageResource(R.drawable.ic_keyboard_backlight)
+ layoutParams =
+ FrameLayout.LayoutParams(iconProperties.width, iconProperties.height).apply {
+ gravity = Gravity.CENTER
+ leftMargin = iconProperties.leftMargin
+ }
+ }
+ }
+
+ private fun setWindowTitle() {
+ val attrs = window.attributes
+ // TODO(b/271796169): check if title needs to be a translatable resource.
+ attrs.title = "KeyboardBacklightDialog"
+ attrs?.y = dialogBottomMargin
+ window.attributes = attrs
+ }
+
+ private fun setUpWindowProperties(dialog: Dialog) {
+ val window = dialog.window
+ window.requestFeature(Window.FEATURE_NO_TITLE) // otherwise fails while creating actionBar
+ window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+ window.addFlags(
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM or
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ )
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ window.setBackgroundDrawableResource(android.R.color.transparent)
+ window.setGravity(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
+ setCanceledOnTouchOutside(true)
+ }
+
+ private fun radiiForIndex(i: Int, last: Int): FloatArray {
+ val smallRadius = stepProperties.smallRadius
+ val largeRadius = stepProperties.largeRadius
+ return when (i) {
+ 0 -> // left radii bigger
+ floatArrayOf(
+ largeRadius,
+ largeRadius,
+ smallRadius,
+ smallRadius,
+ smallRadius,
+ smallRadius,
+ largeRadius,
+ largeRadius
+ )
+ last -> // right radii bigger
+ floatArrayOf(
+ smallRadius,
+ smallRadius,
+ largeRadius,
+ largeRadius,
+ largeRadius,
+ largeRadius,
+ smallRadius,
+ smallRadius
+ )
+ else -> FloatArray(8) { smallRadius } // all radii equal
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
index faeb485..a2589d3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
@@ -52,6 +52,7 @@
cancelAction: Runnable?,
)
fun willDismissWithActions(): Boolean
+ fun willRunDismissFromKeyguard(): Boolean
/** @return the {@link OnBackAnimationCallback} to animate Bouncer during a back gesture. */
fun getBackCallback(): OnBackAnimationCallback
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 2dc8fee..1fbfff9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -47,6 +47,7 @@
listenForOccludedToLockscreen()
listenForOccludedToDreaming()
listenForOccludedToAodOrDozing()
+ listenForOccludedToGone()
}
private fun listenForOccludedToDreaming() {
@@ -72,11 +73,22 @@
private fun listenForOccludedToLockscreen() {
scope.launch {
keyguardInteractor.isKeyguardOccluded
- .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
- .collect { (isOccluded, lastStartedKeyguardState) ->
+ .sample(
+ combine(
+ keyguardInteractor.isKeyguardShowing,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { (isOccluded, isShowing, lastStartedKeyguardState) ->
// Occlusion signals come from the framework, and should interrupt any
// existing transition
- if (!isOccluded && lastStartedKeyguardState.to == KeyguardState.OCCLUDED) {
+ if (
+ !isOccluded &&
+ isShowing &&
+ lastStartedKeyguardState.to == KeyguardState.OCCLUDED
+ ) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
@@ -90,6 +102,38 @@
}
}
+ private fun listenForOccludedToGone() {
+ scope.launch {
+ keyguardInteractor.isKeyguardOccluded
+ .sample(
+ combine(
+ keyguardInteractor.isKeyguardShowing,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { (isOccluded, isShowing, lastStartedKeyguardState) ->
+ // Occlusion signals come from the framework, and should interrupt any
+ // existing transition
+ if (
+ !isOccluded &&
+ !isShowing &&
+ lastStartedKeyguardState.to == KeyguardState.OCCLUDED
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.OCCLUDED,
+ KeyguardState.GONE,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
private fun listenForOccludedToAodOrDozing() {
scope.launch {
keyguardInteractor.wakefulnessModel
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index ec99049..c42e502 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -142,6 +142,8 @@
val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
/** Observable for the [StatusBarState] */
val statusBarState: Flow<StatusBarState> = repository.statusBarState
+ /** Whether or not quick settings or quick quick settings are showing. */
+ val isQuickSettingsVisible: Flow<Boolean> = repository.isQuickSettingsVisible
/**
* Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
* side, under display) is used to unlock the device.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 1735b5d..7064827 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -96,8 +96,9 @@
quickAffordanceAlwaysVisible(position),
keyguardInteractor.isDozing,
keyguardInteractor.isKeyguardShowing,
- ) { affordance, isDozing, isKeyguardShowing ->
- if (!isDozing && isKeyguardShowing) {
+ keyguardInteractor.isQuickSettingsVisible
+ ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible ->
+ if (!isDozing && isKeyguardShowing && !isQuickSettingsVisible) {
affordance
} else {
KeyguardQuickAffordanceModel.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index edd2897..3d2c472 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -120,21 +120,24 @@
val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing
- init {
- keyguardUpdateMonitor.registerCallback(
- object : KeyguardUpdateMonitorCallback() {
- override fun onBiometricRunningStateChanged(
- running: Boolean,
- biometricSourceType: BiometricSourceType?
- ) {
- updateSideFpsVisibility()
- }
+ /**
+ * This callback needs to be a class field so it does not get garbage collected.
+ */
+ val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
+ override fun onBiometricRunningStateChanged(
+ running: Boolean,
+ biometricSourceType: BiometricSourceType?
+ ) {
+ updateSideFpsVisibility()
+ }
- override fun onStrongAuthStateChanged(userId: Int) {
- updateSideFpsVisibility()
- }
- }
- )
+ override fun onStrongAuthStateChanged(userId: Int) {
+ updateSideFpsVisibility()
+ }
+ }
+
+ init {
+ keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
}
// TODO(b/243685699): Move isScrimmed logic to data layer.
@@ -369,6 +372,11 @@
return primaryBouncerView.delegate?.willDismissWithActions() == true
}
+ /** Will the dismissal run from the keyguard layout (instead of from bouncer) */
+ fun willRunDismissFromKeyguard(): Boolean {
+ return primaryBouncerView.delegate?.willRunDismissFromKeyguard() == true
+ }
+
/** Returns whether the bouncer should be full screen. */
private fun needsFullscreenBouncer(): Boolean {
val mode: KeyguardSecurityModel.SecurityMode =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScrimAlpha.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScrimAlpha.kt
new file mode 100644
index 0000000..1db7733
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScrimAlpha.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 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.keyguard.shared.model
+
+/** Alpha values for scrim updates */
+data class ScrimAlpha(
+ val frontAlpha: Float = 0f,
+ val behindAlpha: Float = 0f,
+ val notificationsAlpha: Float = 0f,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index 2337ffc..bb617bd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -97,6 +97,10 @@
override fun willDismissWithActions(): Boolean {
return securityContainerController.hasDismissActions()
}
+
+ override fun willRunDismissFromKeyguard(): Boolean {
+ return securityContainerController.willRunDismissFromKeyguard()
+ }
}
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 92038e2..b23247c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -20,11 +20,14 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.statusbar.SysuiStatusBarStateController
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
/**
* Breaks down PRIMARY_BOUNCER->GONE transition into discrete steps for corresponding views to
@@ -36,6 +39,7 @@
constructor(
private val interactor: KeyguardTransitionInteractor,
private val statusBarStateController: SysuiStatusBarStateController,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
) {
private val transitionAnimation =
KeyguardTransitionAnimationFlow(
@@ -44,26 +48,49 @@
)
private var leaveShadeOpen: Boolean = false
+ private var willRunDismissFromKeyguard: Boolean = false
/** Bouncer container alpha */
val bouncerAlpha: Flow<Float> =
transitionAnimation.createFlow(
duration = 200.milliseconds,
- onStep = { 1f - it },
- )
-
- /** Scrim behind alpha */
- val scrimBehindAlpha: Flow<Float> =
- transitionAnimation.createFlow(
- duration = TO_GONE_DURATION,
- interpolator = EMPHASIZED_ACCELERATE,
- onStart = { leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide() },
+ onStart = {
+ willRunDismissFromKeyguard = primaryBouncerInteractor.willRunDismissFromKeyguard()
+ },
onStep = {
- if (leaveShadeOpen) {
- 1f
+ if (willRunDismissFromKeyguard) {
+ 0f
} else {
1f - it
}
},
)
+
+ /** Scrim alpha values */
+ val scrimAlpha: Flow<ScrimAlpha> =
+ transitionAnimation
+ .createFlow(
+ duration = TO_GONE_DURATION,
+ interpolator = EMPHASIZED_ACCELERATE,
+ onStart = {
+ leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
+ willRunDismissFromKeyguard =
+ primaryBouncerInteractor.willRunDismissFromKeyguard()
+ },
+ onStep = { 1f - it },
+ )
+ .map {
+ if (willRunDismissFromKeyguard) {
+ ScrimAlpha(
+ notificationsAlpha = 1f,
+ )
+ } else if (leaveShadeOpen) {
+ ScrimAlpha(
+ behindAlpha = 1f,
+ notificationsAlpha = 1f,
+ )
+ } else {
+ ScrimAlpha(behindAlpha = it)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceStateAutoRotationLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceStateAutoRotationLog.java
new file mode 100644
index 0000000..beb725e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceStateAutoRotationLog.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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 java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface DeviceStateAutoRotationLog {
+}
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 ca1ed1f..d246b35e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -370,6 +370,16 @@
}
/**
+ * Provides a {@link LogBuffer} for Device State Auto-Rotation logs.
+ */
+ @Provides
+ @SysUISingleton
+ @DeviceStateAutoRotationLog
+ public static LogBuffer provideDeviceStateAutoRotationLogBuffer(LogBufferFactory factory) {
+ return factory.create("DeviceStateAutoRotationLog", 100);
+ }
+
+ /**
* Provides a {@link LogBuffer} for bluetooth-related logs.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 72c4aab..4cc0410 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -1346,13 +1346,9 @@
fun onNotificationRemoved(key: String) {
Assert.isMainThread()
val removed = mediaEntries.remove(key) ?: return
- val isEligibleForResume =
- removed.isLocalSession() ||
- (mediaFlags.isRemoteResumeAllowed() &&
- removed.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE)
if (keyguardUpdateMonitor.isUserInLockdown(removed.userId)) {
logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
- } else if (useMediaResumption && removed.resumeAction != null && isEligibleForResume) {
+ } else if (isAbleToResume(removed)) {
convertToResumePlayer(key, removed)
} else if (mediaFlags.isRetainingPlayersEnabled()) {
handlePossibleRemoval(key, removed, notificationRemoved = true)
@@ -1372,6 +1368,14 @@
handlePossibleRemoval(key, updated)
}
+ private fun isAbleToResume(data: MediaData): Boolean {
+ val isEligibleForResume =
+ data.isLocalSession() ||
+ (mediaFlags.isRemoteResumeAllowed() &&
+ data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE)
+ return useMediaResumption && data.resumeAction != null && isEligibleForResume
+ }
+
/**
* Convert to resume state if the player is no longer valid and active, then notify listeners
* that the data was updated. Does not convert to resume state if the player is still valid, or
@@ -1394,8 +1398,9 @@
if (DEBUG) Log.d(TAG, "Session destroyed but using notification actions $key")
mediaEntries.put(key, removed)
notifyMediaDataLoaded(key, key, removed)
- } else if (removed.active) {
- // This player was still active - it didn't last long enough to time out: remove
+ } else if (removed.active && !isAbleToResume(removed)) {
+ // This player was still active - it didn't last long enough to time out,
+ // and its app doesn't normally support resume: remove
if (DEBUG) Log.d(TAG, "Removing still-active player $key")
notifyMediaDataRemoved(key)
logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
index 92e0c85..b0389b5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
@@ -239,6 +239,8 @@
data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE)
if (data.resumeAction == null && !data.hasCheckedForResume && isEligibleForResume) {
// TODO also check for a media button receiver intended for restarting (b/154127084)
+ // Set null action to prevent additional attempts to connect
+ mediaDataManager.setResumeAction(key, null)
Log.d(TAG, "Checking for service component for " + data.packageName)
val pm = context.packageManager
val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE)
@@ -249,9 +251,6 @@
backgroundExecutor.execute {
tryUpdateResumptionList(key, inf!!.get(0).componentInfo.componentName)
}
- } else {
- // No service found
- mediaDataManager.setResumeAction(key, null)
}
}
}
@@ -263,8 +262,6 @@
*/
private fun tryUpdateResumptionList(key: String, componentName: ComponentName) {
Log.d(TAG, "Testing if we can connect to $componentName")
- // Set null action to prevent additional attempts to connect
- mediaDataManager.setResumeAction(key, null)
mediaBrowser =
mediaBrowserFactory.create(
object : ResumeMediaBrowser.Callback() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
index 3493b24..d460b5b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
@@ -85,16 +85,13 @@
* ResumeMediaBrowser#disconnect will be called automatically with this function.
*/
public void findRecentMedia() {
- disconnect();
Bundle rootHints = new Bundle();
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
- mMediaBrowser = mBrowserFactory.create(
+ MediaBrowser browser = mBrowserFactory.create(
mComponentName,
mConnectionCallback,
rootHints);
- updateMediaController();
- mLogger.logConnection(mComponentName, "findRecentMedia");
- mMediaBrowser.connect();
+ connectBrowser(browser, "findRecentMedia");
}
private final MediaBrowser.SubscriptionCallback mSubscriptionCallback =
@@ -202,6 +199,21 @@
};
/**
+ * Connect using a new media browser. Disconnects the existing browser first, if it exists.
+ * @param browser media browser to connect
+ * @param reason Reason to log for connection
+ */
+ private void connectBrowser(MediaBrowser browser, String reason) {
+ mLogger.logConnection(mComponentName, reason);
+ disconnect();
+ mMediaBrowser = browser;
+ if (browser != null) {
+ browser.connect();
+ }
+ updateMediaController();
+ }
+
+ /**
* Disconnect the media browser. This should be done after callbacks have completed to
* disconnect from the media browser service.
*/
@@ -222,10 +234,9 @@
* getting a media update from the app
*/
public void restart() {
- disconnect();
Bundle rootHints = new Bundle();
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
- mMediaBrowser = mBrowserFactory.create(mComponentName,
+ MediaBrowser browser = mBrowserFactory.create(mComponentName,
new MediaBrowser.ConnectionCallback() {
@Override
public void onConnected() {
@@ -265,9 +276,7 @@
disconnect();
}
}, rootHints);
- updateMediaController();
- mLogger.logConnection(mComponentName, "restart");
- mMediaBrowser.connect();
+ connectBrowser(browser, "restart");
}
@VisibleForTesting
@@ -305,16 +314,13 @@
* ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed.
*/
public void testConnection() {
- disconnect();
Bundle rootHints = new Bundle();
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
- mMediaBrowser = mBrowserFactory.create(
+ MediaBrowser browser = mBrowserFactory.create(
mComponentName,
mConnectionCallback,
rootHints);
- updateMediaController();
- mLogger.logConnection(mComponentName, "testConnection");
- mMediaBrowser.connect();
+ connectBrowser(browser, "testConnection");
}
/** Updates mMediaController based on our current browser values. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index 680a8b6..67d3be4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -197,7 +197,6 @@
private val configListener =
object : ConfigurationController.ConfigurationListener {
- var lastOrientation = -1
override fun onDensityOrFontScaleChanged() {
// System font changes should only happen when UMO is offscreen or a flicker may
@@ -214,13 +213,6 @@
override fun onConfigChanged(newConfig: Configuration?) {
if (newConfig == null) return
isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL
- val newOrientation = newConfig.orientation
- if (lastOrientation != newOrientation) {
- // The players actually depend on the orientation possibly, so we have to
- // recreate them (at least on large screen devices)
- lastOrientation = newOrientation
- updatePlayers(recreateMedia = true)
- }
}
override fun onUiModeChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 0788e61..b4724dd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -154,9 +154,11 @@
return transitionLayout?.translationY ?: 0.0f
}
- /** A callback for RTL config changes */
+ /** A callback for config changes */
private val configurationListener =
object : ConfigurationController.ConfigurationListener {
+ var lastOrientation = -1
+
override fun onConfigChanged(newConfig: Configuration?) {
// Because the TransitionLayout is not always attached (and calculates/caches layout
// results regardless of attach state), we have to force the layoutDirection of the
@@ -169,6 +171,13 @@
transitionLayout?.layoutDirection = layoutDirection
refreshState()
}
+ val newOrientation = newConfig.orientation
+ if (lastOrientation != newOrientation) {
+ // Layout dimensions are possibly changing, so we need to update them. (at
+ // least on large screen devices)
+ lastOrientation = newOrientation
+ loadLayoutForType(type)
+ }
}
}
}
@@ -195,13 +204,14 @@
* The expanded constraint set used to render a expanded player. If it is modified, make sure to
* call [refreshState]
*/
- val collapsedLayout = ConstraintSet()
-
+ var collapsedLayout = ConstraintSet()
+ @VisibleForTesting set
/**
* The expanded constraint set used to render a collapsed player. If it is modified, make sure
* to call [refreshState]
*/
- val expandedLayout = ConstraintSet()
+ var expandedLayout = ConstraintSet()
+ @VisibleForTesting set
/** Whether the guts are visible for the associated player. */
var isGutsVisible = false
@@ -483,7 +493,7 @@
*/
fun attach(transitionLayout: TransitionLayout, type: TYPE) =
traceSection("MediaViewController#attach") {
- updateMediaViewControllerType(type)
+ loadLayoutForType(type)
logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation)
this.transitionLayout = transitionLayout
layoutController.attach(transitionLayout)
@@ -641,7 +651,7 @@
return result
}
- private fun updateMediaViewControllerType(type: TYPE) {
+ private fun loadLayoutForType(type: TYPE) {
this.type = type
// These XML resources contain ConstraintSets that will apply to this player type's layout
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 006cedf..e4351d2 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -25,11 +25,6 @@
import static android.app.StatusBarManager.WindowVisibleState;
import static android.app.StatusBarManager.windowStateToString;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
-import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
-import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
-import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
@@ -292,6 +287,7 @@
private final DeadZone mDeadZone;
private boolean mImeVisible;
private final Rect mSamplingBounds = new Rect();
+ private final Binder mInsetsSourceOwner = new Binder();
/**
* When quickswitching between apps of different orientations, we draw a secondary home handle
@@ -1709,28 +1705,21 @@
}
private InsetsFrameProvider[] getInsetsFrameProvider(int insetsHeight, Context userContext) {
- final InsetsFrameProvider navBarProvider;
+ final InsetsFrameProvider navBarProvider =
+ new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.navigationBars())
+ .setInsetsSizeOverrides(new InsetsFrameProvider.InsetsSizeOverride[] {
+ new InsetsFrameProvider.InsetsSizeOverride(
+ TYPE_INPUT_METHOD, null)});
if (insetsHeight != -1 && !mIsButtonForceVisible) {
- navBarProvider = new InsetsFrameProvider(
- ITYPE_NAVIGATION_BAR, Insets.of(0, 0, 0, insetsHeight));
- // Use window frame for IME.
- navBarProvider.insetsSizeOverrides = new InsetsFrameProvider.InsetsSizeOverride[] {
- new InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, null)
- };
- } else {
- navBarProvider = new InsetsFrameProvider(ITYPE_NAVIGATION_BAR);
- navBarProvider.insetsSizeOverrides = new InsetsFrameProvider.InsetsSizeOverride[]{
- new InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, null)
- };
+ navBarProvider.setInsetsSize(Insets.of(0, 0, 0, insetsHeight));
}
+
+ final InsetsFrameProvider tappableElementProvider = new InsetsFrameProvider(
+ mInsetsSourceOwner, 0, WindowInsets.Type.tappableElement());
final boolean navBarTapThrough = userContext.getResources().getBoolean(
com.android.internal.R.bool.config_navBarTapThrough);
- final InsetsFrameProvider bottomTappableProvider;
if (navBarTapThrough) {
- bottomTappableProvider = new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT,
- Insets.of(0, 0, 0, 0));
- } else {
- bottomTappableProvider = new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT);
+ tappableElementProvider.setInsetsSize(Insets.NONE);
}
final DisplayCutout cutout = userContext.getDisplay().getCutout();
@@ -1745,13 +1734,16 @@
? mEdgeBackGestureHandler.getEdgeWidthRight() + safeInsetsRight : 0;
return new InsetsFrameProvider[] {
navBarProvider,
+ tappableElementProvider,
new InsetsFrameProvider(
- ITYPE_BOTTOM_MANDATORY_GESTURES, Insets.of(0, 0, 0, gestureHeight)),
- new InsetsFrameProvider(ITYPE_LEFT_GESTURES, InsetsFrameProvider.SOURCE_DISPLAY,
- Insets.of(gestureInsetsLeft, 0, 0, 0), null),
- new InsetsFrameProvider(ITYPE_RIGHT_GESTURES, InsetsFrameProvider.SOURCE_DISPLAY,
- Insets.of(0, 0, gestureInsetsRight, 0), null),
- bottomTappableProvider
+ mInsetsSourceOwner, 0, WindowInsets.Type.mandatorySystemGestures())
+ .setInsetsSize(Insets.of(0, 0, 0, gestureHeight)),
+ new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.systemGestures())
+ .setSource(InsetsFrameProvider.SOURCE_DISPLAY)
+ .setInsetsSize(Insets.of(gestureInsetsLeft, 0, 0, 0)),
+ new InsetsFrameProvider(mInsetsSourceOwner, 1, WindowInsets.Type.systemGestures())
+ .setSource(InsetsFrameProvider.SOURCE_DISPLAY)
+ .setInsetsSize(Insets.of(0, 0, gestureInsetsRight, 0))
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
index f335733..70040c7 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
@@ -2,19 +2,15 @@
import android.content.Context
import android.content.res.Configuration
-import android.content.res.TypedArray
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
import android.util.MathUtils.min
-import android.util.TypedValue
import android.view.View
-import androidx.appcompat.view.ContextThemeWrapper
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
-import com.android.internal.R.style.Theme_DeviceDefault
import com.android.internal.util.LatencyTracker
import com.android.settingslib.Utils
import com.android.systemui.navigationbar.gestural.BackPanelController.DelayedOnAnimationEndListener
@@ -159,26 +155,21 @@
val isDeviceInNightTheme = resources.configuration.uiMode and
Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
- val colorControlActivated = ContextThemeWrapper(context, Theme_DeviceDefault)
- .run {
- val typedValue = TypedValue()
- val a: TypedArray = obtainStyledAttributes(typedValue.data,
- intArrayOf(android.R.attr.colorControlActivated))
- val color = a.getColor(0, 0)
- a.recycle()
- color
+ arrowPaint.color = Utils.getColorAttrDefaultColor(context,
+ if (isDeviceInNightTheme) {
+ com.android.internal.R.attr.colorAccentPrimary
+ } else {
+ com.android.internal.R.attr.textColorPrimary
}
+ )
- val colorPrimary =
- Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
-
- arrowPaint.color = Utils.getColorAccentDefaultColor(context)
-
- arrowBackgroundPaint.color = if (isDeviceInNightTheme) {
- colorPrimary
- } else {
- colorControlActivated
- }
+ arrowBackgroundPaint.color = Utils.getColorAttrDefaultColor(context,
+ if (isDeviceInNightTheme) {
+ com.android.internal.R.attr.materialColorOnSecondary
+ } else {
+ com.android.internal.R.attr.colorAccentSecondary
+ }
+ )
}
inner class AnimatedFloat(
@@ -414,9 +405,9 @@
) {
horizontalTranslation.updateRestingPosition(restingParams.horizontalTranslation)
scale.updateRestingPosition(restingParams.scale)
- arrowAlpha.updateRestingPosition(restingParams.arrowDimens.alpha)
backgroundAlpha.updateRestingPosition(restingParams.backgroundDimens.alpha)
+ arrowAlpha.updateRestingPosition(restingParams.arrowDimens.alpha, animate)
arrowLength.updateRestingPosition(restingParams.arrowDimens.length, animate)
arrowHeight.updateRestingPosition(restingParams.arrowDimens.height, animate)
scalePivotX.updateRestingPosition(restingParams.backgroundDimens.width, animate)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index f409b23..80ed08c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -55,12 +55,12 @@
internal const val MIN_DURATION_ACTIVE_ANIMATION = 300L
private const val MIN_DURATION_CANCELLED_ANIMATION = 200L
-private const val MIN_DURATION_COMMITTED_ANIMATION = 200L
+private const val MIN_DURATION_COMMITTED_ANIMATION = 120L
private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L
private const val MIN_DURATION_CONSIDERED_AS_FLING = 100L
private const val FAILSAFE_DELAY_MS = 350L
-private const val POP_ON_FLING_DELAY = 160L
+private const val POP_ON_FLING_DELAY = 140L
internal val VIBRATE_ACTIVATED_EFFECT =
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
@@ -148,8 +148,6 @@
private var gestureSinceActionDown = 0L
private var gestureEntryTime = 0L
private var gestureActiveTime = 0L
- private var gestureInactiveOrEntryTime = 0L
- private var gestureArrowStrokeVisibleTime = 0L
private val elapsedTimeSinceActionDown
get() = SystemClock.uptimeMillis() - gestureSinceActionDown
@@ -441,34 +439,44 @@
updateArrowStateOnMove(yTranslation, xTranslation)
- when (currentState) {
- GestureState.ACTIVE -> {
- stretchActiveBackIndicator(fullScreenProgress(xTranslation))
- }
- GestureState.ENTRY -> {
- val progress = staticThresholdProgress(xTranslation)
- stretchEntryBackIndicator(progress)
-
- params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let {
- mView.popArrowAlpha(0f, it.value)
- }
- }
- GestureState.INACTIVE -> {
- val progress = reactivationThresholdProgress(totalTouchDelta)
- stretchInactiveBackIndicator(progress)
-
- params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let {
- gestureArrowStrokeVisibleTime = SystemClock.uptimeMillis()
- mView.popArrowAlpha(0f, it.value)
- }
- }
- else -> {}
+ val gestureProgress = when (currentState) {
+ GestureState.ACTIVE -> fullScreenProgress(xTranslation)
+ GestureState.ENTRY -> staticThresholdProgress(xTranslation)
+ GestureState.INACTIVE -> reactivationThresholdProgress(totalTouchDelta)
+ else -> null
}
- // set y translation
+ gestureProgress?.let {
+ when (currentState) {
+ GestureState.ACTIVE -> stretchActiveBackIndicator(gestureProgress)
+ GestureState.ENTRY -> stretchEntryBackIndicator(gestureProgress)
+ GestureState.INACTIVE -> stretchInactiveBackIndicator(gestureProgress)
+ else -> {}
+ }
+ }
+
+ setArrowStrokeAlpha(gestureProgress)
setVerticalTranslation(yOffset)
}
+ private fun setArrowStrokeAlpha(gestureProgress: Float?) {
+ val strokeAlphaProgress = when (currentState) {
+ GestureState.ENTRY -> gestureProgress
+ GestureState.INACTIVE -> gestureProgress
+ GestureState.ACTIVE,
+ GestureState.FLUNG,
+ GestureState.COMMITTED -> 1f
+ GestureState.CANCELLED,
+ GestureState.GONE -> 0f
+ }
+
+ strokeAlphaProgress?.let { progress ->
+ params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let {
+ mView.popArrowAlpha(0f, it.value)
+ }
+ }
+ }
+
private fun setVerticalTranslation(yOffset: Float) {
val yTranslation = abs(yOffset)
val maxYOffset = (mView.height - params.entryIndicator.backgroundDimens.height) / 2f
@@ -599,7 +607,7 @@
private fun isFlungAwayFromEdge(endX: Float, startX: Float = touchDeltaStartX): Boolean {
val minDistanceConsideredForFling = ViewConfiguration.get(context).scaledTouchSlop
- val flingDistance = abs(endX - startX)
+ val flingDistance = if (mView.isLeftPanel) endX - startX else startX - endX
val isPastFlingVelocity = isDragAwayFromEdge(
velocityPxPerSecThreshold =
ViewConfiguration.get(context).scaledMinimumFlingVelocity)
@@ -764,7 +772,7 @@
GestureState.ENTRY,
GestureState.INACTIVE -> params.entryIndicator.arrowDimens
GestureState.ACTIVE -> params.activeIndicator.arrowDimens
- GestureState.FLUNG,
+ GestureState.FLUNG -> params.flungIndicator.arrowDimens
GestureState.COMMITTED -> params.committedIndicator.arrowDimens
GestureState.CANCELLED -> params.cancelledIndicator.arrowDimens
},
@@ -825,7 +833,6 @@
updateRestingArrowDimens()
gestureEntryTime = SystemClock.uptimeMillis()
- gestureInactiveOrEntryTime = SystemClock.uptimeMillis()
}
GestureState.ACTIVE -> {
previousXTranslationOnActiveOffset = previousXTranslation
@@ -857,7 +864,13 @@
}
GestureState.INACTIVE -> {
- gestureInactiveOrEntryTime = SystemClock.uptimeMillis()
+
+ // Typically entering INACTIVE means
+ // totalTouchDelta <= deactivationSwipeTriggerThreshold
+ // but because we can also independently enter this state
+ // if touch Y >> touch X, we force it to deactivationSwipeTriggerThreshold
+ // so that gesture progress in this state is consistent regardless of entry
+ totalTouchDelta = params.deactivationSwipeTriggerThreshold
val startingVelocity = convertVelocityToSpringStartingVelocity(
valueOnFastVelocity = -1.05f,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
index 0c00022..d46333a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -9,8 +9,8 @@
data class EdgePanelParams(private var resources: Resources) {
data class ArrowDimens(
- val length: Float = 0f,
- val height: Float = 0f,
+ val length: Float? = 0f,
+ val height: Float? = 0f,
val alpha: Float = 0f,
var alphaSpring: SpringForce? = null,
val heightSpring: SpringForce? = null,
@@ -139,17 +139,17 @@
entryWidthInterpolator = PathInterpolator(.19f, 1.27f, .71f, .86f)
entryWidthTowardsEdgeInterpolator = PathInterpolator(1f, -3f, 1f, 1.2f)
- activeWidthInterpolator = PathInterpolator(.15f, .48f, .46f, .89f)
+ activeWidthInterpolator = PathInterpolator(.32f, 0f, .16f, .94f)
arrowAngleInterpolator = entryWidthInterpolator
translationInterpolator = PathInterpolator(0.2f, 1.0f, 1.0f, 1.0f)
farCornerInterpolator = PathInterpolator(.03f, .19f, .14f, 1.09f)
edgeCornerInterpolator = PathInterpolator(0f, 1.11f, .85f, .84f)
heightInterpolator = PathInterpolator(1f, .05f, .9f, -0.29f)
- val showArrowOnProgressValue = .2f
+ val showArrowOnProgressValue = .23f
val showArrowOnProgressValueFactor = 1.05f
- val entryActiveHorizontalTranslationSpring = createSpring(675f, 0.8f)
+ val entryActiveHorizontalTranslationSpring = createSpring(800f, 0.8f)
val activeCommittedArrowLengthSpring = createSpring(1500f, 0.29f)
val activeCommittedArrowHeightSpring = createSpring(1500f, 0.29f)
val flungCommittedEdgeCornerSpring = createSpring(10000f, 1f)
@@ -178,7 +178,7 @@
height = getDimen(R.dimen.navigation_edge_entry_background_height),
edgeCornerRadius = getDimen(R.dimen.navigation_edge_entry_edge_corners),
farCornerRadius = getDimen(R.dimen.navigation_edge_entry_far_corners),
- alphaSpring = createSpring(900f, 1f),
+ alphaSpring = createSpring(1100f, 1f),
widthSpring = createSpring(450f, 0.65f),
heightSpring = createSpring(1500f, 0.45f),
farCornerRadiusSpring = createSpring(300f, 0.5f),
@@ -232,7 +232,7 @@
getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners),
farCornerRadius =
getDimen(R.dimen.navigation_edge_pre_threshold_far_corners),
- widthSpring = createSpring(200f, 0.65f),
+ widthSpring = createSpring(250f, 0.65f),
heightSpring = createSpring(1500f, 0.45f),
farCornerRadiusSpring = createSpring(200f, 1f),
edgeCornerRadiusSpring = createSpring(150f, 0.5f),
@@ -244,6 +244,8 @@
arrowDimens = activeIndicator.arrowDimens.copy(
lengthSpring = activeCommittedArrowLengthSpring,
heightSpring = activeCommittedArrowHeightSpring,
+ length = null,
+ height = null,
),
backgroundDimens = activeIndicator.backgroundDimens.copy(
alpha = 0f,
@@ -255,13 +257,15 @@
farCornerRadiusSpring = flungCommittedFarCornerSpring,
),
scale = 0.85f,
- scaleSpring = createSpring(650f, 1f),
+ scaleSpring = createSpring(1150f, 1f),
)
flungIndicator = committedIndicator.copy(
arrowDimens = committedIndicator.arrowDimens.copy(
lengthSpring = createSpring(850f, 0.46f),
heightSpring = createSpring(850f, 0.46f),
+ length = activeIndicator.arrowDimens.length,
+ height = activeIndicator.arrowDimens.height
),
backgroundDimens = committedIndicator.backgroundDimens.copy(
widthSpring = flungCommittedWidthSpring,
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index c65f0aa..6cd04c8 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -22,6 +22,9 @@
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.PackageManager
import android.os.Build
import android.os.UserManager
@@ -54,8 +57,8 @@
private val resolver: NoteTaskInfoResolver,
private val eventLogger: NoteTaskEventLogger,
private val optionalBubbles: Optional<Bubbles>,
- private val optionalUserManager: Optional<UserManager>,
- private val optionalKeyguardManager: Optional<KeyguardManager>,
+ private val userManager: UserManager,
+ private val keyguardManager: KeyguardManager,
@NoteTaskEnabledKey private val isEnabled: Boolean,
private val devicePolicyManager: DevicePolicyManager,
private val userTracker: UserTracker,
@@ -106,8 +109,6 @@
if (!isEnabled) return
val bubbles = optionalBubbles.getOrNull() ?: return
- val userManager = optionalUserManager.getOrNull() ?: return
- val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
// TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
if (!userManager.isUserUnlocked) return
@@ -140,12 +141,13 @@
logDebug { "onShowNoteTask - start: $info" }
when (info.launchMode) {
is NoteTaskLaunchMode.AppBubble -> {
+ // TODO(b/267634412, b/268351693): Should use `showOrHideAppBubbleAsUser`
bubbles.showOrHideAppBubble(intent)
// App bubble logging happens on `onBubbleExpandChanged`.
logDebug { "onShowNoteTask - opened as app bubble: $info" }
}
is NoteTaskLaunchMode.Activity -> {
- context.startActivity(intent)
+ context.startActivityAsUser(intent, userTracker.userHandle)
eventLogger.logNoteTaskOpened(info)
logDebug { "onShowNoteTask - opened as activity: $info" }
}
@@ -185,25 +187,25 @@
companion object {
val TAG = NoteTaskController::class.simpleName.orEmpty()
-
- // TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead.
- const val NOTE_TASK_KEY_EVENT = 311
-
- // TODO(b/265912743): Use Intent.ACTION_CREATE_NOTE instead.
- const val ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE"
-
- // TODO(b/265912743): Use Intent.INTENT_EXTRA_USE_STYLUS_MODE instead.
- const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE"
}
}
private fun createNoteIntent(info: NoteTaskInfo): Intent =
- Intent(NoteTaskController.ACTION_CREATE_NOTE)
- .setPackage(info.packageName)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ Intent(Intent.ACTION_CREATE_NOTE).apply {
+ setPackage(info.packageName)
+
// EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint
// was used to start it.
- .putExtra(NoteTaskController.INTENT_EXTRA_USE_STYLUS_MODE, true)
+ putExtra(Intent.EXTRA_USE_STYLUS_MODE, true)
+
+ addFlags(FLAG_ACTIVITY_NEW_TASK)
+ // We should ensure the note experience can be open both as a full screen (lock screen)
+ // and inside the app bubble (contextual). These additional flags will do that.
+ if (info.launchMode == NoteTaskLaunchMode.Activity) {
+ addFlags(FLAG_ACTIVITY_MULTIPLE_TASK)
+ addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+ }
+ }
private inline fun logDebug(message: () -> String) {
if (Build.IS_DEBUGGABLE) {
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
index 0f75f95..b98a0fd 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
@@ -17,19 +17,19 @@
package com.android.systemui.notetask
import android.app.role.RoleManager
-import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.os.UserHandle
import android.util.Log
+import com.android.systemui.settings.UserTracker
import javax.inject.Inject
class NoteTaskInfoResolver
@Inject
constructor(
- private val context: Context,
private val roleManager: RoleManager,
private val packageManager: PackageManager,
+ private val userTracker: UserTracker,
) {
fun resolveInfo(
@@ -38,7 +38,7 @@
isKeyguardLocked: Boolean = false,
): NoteTaskInfo? {
// TODO(b/267634412): Select UserHandle depending on where the user initiated note-taking.
- val user = context.user
+ val user = userTracker.userHandle
val packageName =
roleManager.getRoleHoldersAsUser(RoleManager.ROLE_NOTES, user).firstOrNull()
@@ -56,9 +56,6 @@
companion object {
private val TAG = NoteTaskInfoResolver::class.simpleName.orEmpty()
- // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead.
- const val ROLE_NOTES = "android.app.role.NOTES"
-
private val EMPTY_APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0)!!
/**
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index ba8999c..6278c69 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -17,11 +17,7 @@
package com.android.systemui.notetask
import android.app.Activity
-import android.app.KeyguardManager
import android.app.role.RoleManager
-import android.content.Context
-import android.os.UserManager
-import androidx.core.content.getSystemService
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceModule
@@ -32,7 +28,6 @@
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
-import java.util.Optional
/** Compose all dependencies required by Note Task feature. */
@Module(includes = [NoteTaskQuickAffordanceModule::class])
@@ -55,15 +50,5 @@
val isFeatureEnabled = featureFlags.isEnabled(Flags.NOTE_TASKS)
return isRoleAvailable && isFeatureEnabled
}
-
- @Provides
- fun provideOptionalKeyguardManager(context: Context): Optional<KeyguardManager> {
- return Optional.ofNullable(context.getSystemService())
- }
-
- @Provides
- fun provideOptionalUserManager(context: Context): Optional<UserManager> {
- return Optional.ofNullable(context.getSystemService())
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index e55445c..baa812c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -50,6 +50,8 @@
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.compose.ComposeFacade;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QSContainerController;
@@ -59,6 +61,7 @@
import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -111,6 +114,8 @@
private final MediaHost mQqsMediaHost;
private final QSFragmentComponent.Factory mQsComponentFactory;
private final QSFragmentDisableFlagsLogger mQsFragmentDisableFlagsLogger;
+ private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+ private final FeatureFlags mFeatureFlags;
private final QSLogger mLogger;
private final FooterActionsController mFooterActionsController;
private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
@@ -158,12 +163,7 @@
// visible;
private boolean mQsVisible;
- /**
- * Whether the notification panel uses the full width of the screen.
- *
- * Usually {@code true} on small screens, and {@code false} on large screens.
- */
- private boolean mIsNotificationPanelFullWidth;
+ private boolean mIsSmallScreen;
@Inject
public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
@@ -175,13 +175,17 @@
QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger,
DumpManager dumpManager, QSLogger qsLogger,
FooterActionsController footerActionsController,
- FooterActionsViewModel.Factory footerActionsViewModelFactory) {
+ FooterActionsViewModel.Factory footerActionsViewModelFactory,
+ LargeScreenShadeInterpolator largeScreenShadeInterpolator,
+ FeatureFlags featureFlags) {
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
mQsMediaHost = qsMediaHost;
mQqsMediaHost = qqsMediaHost;
mQsComponentFactory = qsComponentFactory;
mQsFragmentDisableFlagsLogger = qsFragmentDisableFlagsLogger;
mLogger = qsLogger;
+ mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
+ mFeatureFlags = featureFlags;
commandQueue.observe(getLifecycle(), this);
mBypassController = keyguardBypassController;
mStatusBarStateController = statusBarStateController;
@@ -606,7 +610,7 @@
@Override
public void setIsNotificationPanelFullWidth(boolean isFullWidth) {
- mIsNotificationPanelFullWidth = isFullWidth;
+ mIsSmallScreen = isFullWidth;
}
@Override
@@ -709,7 +713,7 @@
}
private float calculateAlphaProgress(float panelExpansionFraction) {
- if (mIsNotificationPanelFullWidth) {
+ if (mIsSmallScreen) {
// Small screens. QS alpha is not animated.
return 1;
}
@@ -744,7 +748,12 @@
// Alpha progress should be linear on lockscreen shade expansion.
return progress;
}
- return ShadeInterpolation.getContentAlpha(progress);
+ if (mIsSmallScreen || !mFeatureFlags.isEnabled(
+ Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ return ShadeInterpolation.getContentAlpha(progress);
+ } else {
+ return mLargeScreenShadeInterpolator.getQsAlpha(progress);
+ }
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 7c2536d..d4854e1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -328,7 +328,7 @@
if (listening) {
updateDefaultTileAndIcon();
refreshState();
- if (!mServiceManager.isActiveTile()) {
+ if (!mServiceManager.isActiveTile() || !isTileReady()) {
mServiceManager.setBindRequested(true);
mService.onStartListening();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
index ead3b7b..0b4b7c6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
@@ -45,6 +45,7 @@
implements ViewTreeObserver.OnComputeInternalInsetsListener {
private static final float VELOCITY_DP_PER_MS = 1;
+ private static final int MAXIMUM_DISMISS_DISTANCE_DP = 400;
private final SwipeDismissHandler mSwipeDismissHandler;
private final GestureDetector mSwipeDetector;
@@ -347,14 +348,18 @@
} else {
finalX = -1 * getBackgroundRight();
}
- float distance = Math.abs(finalX - startX);
+ float distance = Math.min(Math.abs(finalX - startX),
+ FloatingWindowUtil.dpToPx(mDisplayMetrics, MAXIMUM_DISMISS_DISTANCE_DP));
+ // ensure that view dismisses in the right direction (right in LTR, left in RTL)
+ float distanceVector = Math.copySign(distance, finalX - startX);
anim.addUpdateListener(animation -> {
- float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction());
+ float translation = MathUtils.lerp(
+ startX, startX + distanceVector, animation.getAnimatedFraction());
mView.setTranslationX(translation);
mView.setAlpha(1 - animation.getAnimatedFraction());
});
- anim.setDuration((long) (distance / Math.abs(velocity)));
+ anim.setDuration((long) (Math.abs(distance / velocity)));
return anim;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 8721d71..557e95c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -419,6 +419,10 @@
return;
}
+ mScreenBitmap = screenshot.getBitmap();
+ String oldPackageName = mPackageName;
+ mPackageName = screenshot.getPackageNameString();
+
if (!isUserSetupComplete(Process.myUserHandle())) {
Log.w(TAG, "User setup not complete, displaying toast only");
// User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
@@ -433,10 +437,6 @@
mScreenshotTakenInPortrait =
mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
- String oldPackageName = mPackageName;
- mPackageName = screenshot.getPackageNameString();
-
- mScreenBitmap = screenshot.getBitmap();
// Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b1987c1..4a7dd97 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -17,6 +17,7 @@
package com.android.systemui.shade;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
@@ -305,6 +306,7 @@
*/
public final boolean mAnimateBack;
+ private final boolean mTrackpadGestureBack;
/**
* The minimum scale to "squish" the Shade and associated elements down to, for Back gesture
*/
@@ -849,6 +851,7 @@
mLayoutInflater = layoutInflater;
mFeatureFlags = featureFlags;
mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
+ mTrackpadGestureBack = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_BACK);
mFalsingCollector = falsingCollector;
mPowerManager = powerManager;
mWakeUpCoordinator = coordinator;
@@ -2953,7 +2956,10 @@
mHeadsUpStartHeight = startHeight;
float scrimMinFraction;
if (mSplitShadeEnabled) {
- boolean highHun = mHeadsUpStartHeight * 2.5 > mSplitShadeScrimTransitionDistance;
+ boolean highHun = mHeadsUpStartHeight * 2.5
+ >
+ (mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)
+ ? mSplitShadeFullTransitionDistance : mSplitShadeScrimTransitionDistance);
// if HUN height is higher than 40% of predefined transition distance, it means HUN
// is too high for regular transition. In that case we need to calculate transition
// distance - here we take scrim transition distance as equal to shade transition
@@ -4758,6 +4764,9 @@
addMovement(event);
break;
case MotionEvent.ACTION_POINTER_UP:
+ if (isTrackpadMotionEvent(event)) {
+ break;
+ }
final int upPointer = event.getPointerId(event.getActionIndex());
if (mTrackingPointer == upPointer) {
// gesture is ongoing, find a new pointer to track
@@ -4771,7 +4780,8 @@
mShadeLog.logMotionEventStatusBarState(event,
mStatusBarStateController.getState(),
"onInterceptTouchEvent: pointer down action");
- if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+ if (!isTrackpadMotionEvent(event)
+ && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
mMotionAborted = true;
mVelocityTracker.clear();
}
@@ -4974,6 +4984,9 @@
break;
case MotionEvent.ACTION_POINTER_UP:
+ if (isTrackpadMotionEvent(event)) {
+ break;
+ }
final int upPointer = event.getPointerId(event.getActionIndex());
if (mTrackingPointer == upPointer) {
// gesture is ongoing, find a new pointer to track
@@ -4990,7 +5003,8 @@
mShadeLog.logMotionEventStatusBarState(event,
mStatusBarStateController.getState(),
"handleTouch: pointer down action");
- if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+ if (!isTrackpadMotionEvent(event)
+ && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
mMotionAborted = true;
endMotionEvent(event, x, y, true /* forceCancel */);
return false;
@@ -5064,6 +5078,11 @@
}
return !mGestureWaitForTouchSlop || mTracking;
}
+
+ private boolean isTrackpadMotionEvent(MotionEvent ev) {
+ return mTrackpadGestureBack
+ && ev.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE;
+ }
}
static class SplitShadeTransitionAdapter extends Transition {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenPortraitShadeInterpolator.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenPortraitShadeInterpolator.kt
new file mode 100644
index 0000000..0519131
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenPortraitShadeInterpolator.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 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.shade.transition
+
+import android.util.MathUtils
+import com.android.systemui.animation.ShadeInterpolation
+import javax.inject.Inject
+
+/** Interpolator responsible for the shade when in portrait on a large screen. */
+internal class LargeScreenPortraitShadeInterpolator @Inject internal constructor() :
+ LargeScreenShadeInterpolator {
+
+ override fun getBehindScrimAlpha(fraction: Float): Float {
+ return MathUtils.constrainedMap(0f, 1f, 0f, 0.3f, fraction)
+ }
+
+ override fun getNotificationScrimAlpha(fraction: Float): Float {
+ return MathUtils.constrainedMap(0f, 1f, 0.3f, 0.75f, fraction)
+ }
+
+ override fun getNotificationContentAlpha(fraction: Float): Float {
+ return ShadeInterpolation.getContentAlpha(fraction)
+ }
+
+ override fun getNotificationFooterAlpha(fraction: Float): Float {
+ return ShadeInterpolation.getContentAlpha(fraction)
+ }
+
+ override fun getQsAlpha(fraction: Float): Float {
+ return ShadeInterpolation.getContentAlpha(fraction)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolator.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolator.kt
new file mode 100644
index 0000000..671dfc9c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolator.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 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.shade.transition
+
+/** An interpolator interface for the shade expansion. */
+interface LargeScreenShadeInterpolator {
+
+ /** Returns the alpha for the behind/back scrim. */
+ fun getBehindScrimAlpha(fraction: Float): Float
+
+ /** Returns the alpha for the notification scrim. */
+ fun getNotificationScrimAlpha(fraction: Float): Float
+
+ /** Returns the alpha for the notifications. */
+ fun getNotificationContentAlpha(fraction: Float): Float
+
+ /** Returns the alpha for the notifications footer (Manager, Clear All). */
+ fun getNotificationFooterAlpha(fraction: Float): Float
+
+ /** Returns the alpha for the QS panel. */
+ fun getQsAlpha(fraction: Float): Float
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
new file mode 100644
index 0000000..fd57f21
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 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.shade.transition
+
+import android.content.Context
+import android.content.res.Configuration
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.LargeScreenUtils
+import javax.inject.Inject
+
+/** Interpolator responsible for the shade when on large screens. */
+@SysUISingleton
+internal class LargeScreenShadeInterpolatorImpl
+@Inject
+internal constructor(
+ configurationController: ConfigurationController,
+ private val context: Context,
+ private val splitShadeInterpolator: SplitShadeInterpolator,
+ private val portraitShadeInterpolator: LargeScreenPortraitShadeInterpolator,
+) : LargeScreenShadeInterpolator {
+
+ private var inSplitShade = false
+
+ init {
+ configurationController.addCallback(
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ updateResources()
+ }
+ }
+ )
+ updateResources()
+ }
+
+ private fun updateResources() {
+ inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources)
+ }
+
+ private val impl: LargeScreenShadeInterpolator
+ get() =
+ if (inSplitShade) {
+ splitShadeInterpolator
+ } else {
+ portraitShadeInterpolator
+ }
+
+ override fun getBehindScrimAlpha(fraction: Float) = impl.getBehindScrimAlpha(fraction)
+
+ override fun getNotificationScrimAlpha(fraction: Float) =
+ impl.getNotificationScrimAlpha(fraction)
+
+ override fun getNotificationContentAlpha(fraction: Float) =
+ impl.getNotificationContentAlpha(fraction)
+
+ override fun getNotificationFooterAlpha(fraction: Float) =
+ impl.getNotificationFooterAlpha(fraction)
+
+ override fun getQsAlpha(fraction: Float) = impl.getQsAlpha(fraction)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
index 218e897..4e1c272 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -23,6 +23,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.shade.PanelState
import com.android.systemui.shade.STATE_OPENING
import com.android.systemui.shade.ShadeExpansionChangeEvent
@@ -45,7 +47,8 @@
private val scrimController: ScrimController,
@Main private val resources: Resources,
private val statusBarStateController: SysuiStatusBarStateController,
- private val headsUpManager: HeadsUpManager
+ private val headsUpManager: HeadsUpManager,
+ private val featureFlags: FeatureFlags,
) {
private var inSplitShade = false
@@ -106,7 +109,8 @@
// in case of HUN we can't always use predefined distances to manage scrim
// transition because dragDownPxAmount can start from value bigger than
// splitShadeScrimTransitionDistance
- !headsUpManager.isTrackingHeadsUp
+ !headsUpManager.isTrackingHeadsUp &&
+ !featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)
private fun isScreenUnlocked() =
statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeInterpolator.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeInterpolator.kt
new file mode 100644
index 0000000..423ba8d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeInterpolator.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 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.shade.transition
+
+import android.util.MathUtils
+import javax.inject.Inject
+
+/** Interpolator responsible for the split shade. */
+internal class SplitShadeInterpolator @Inject internal constructor() :
+ LargeScreenShadeInterpolator {
+
+ override fun getBehindScrimAlpha(fraction: Float): Float {
+ // Start delay: 0%
+ // Duration: 40%
+ // End: 40%
+ return mapFraction(start = 0f, end = 0.4f, fraction)
+ }
+
+ override fun getNotificationScrimAlpha(fraction: Float): Float {
+ // Start delay: 39%
+ // Duration: 27%
+ // End: 66%
+ return mapFraction(start = 0.39f, end = 0.66f, fraction)
+ }
+
+ override fun getNotificationContentAlpha(fraction: Float): Float {
+ return getNotificationScrimAlpha(fraction)
+ }
+
+ override fun getNotificationFooterAlpha(fraction: Float): Float {
+ // Start delay: 57.6%
+ // Duration: 32.1%
+ // End: 89.7%
+ return mapFraction(start = 0.576f, end = 0.897f, fraction)
+ }
+
+ override fun getQsAlpha(fraction: Float): Float {
+ return getNotificationScrimAlpha(fraction)
+ }
+
+ private fun mapFraction(start: Float, end: Float, fraction: Float) =
+ MathUtils.constrainedMap(
+ /* rangeMin= */ 0f,
+ /* rangeMax= */ 1f,
+ /* valueMin= */ start,
+ /* valueMax= */ end,
+ /* value= */ fraction
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 8f1e0a1..3709a13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -39,7 +39,10 @@
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.notification.LegacySourceType;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.SourceType;
@@ -216,7 +219,15 @@
if (ambientState.isBouncerInTransit()) {
viewState.setAlpha(aboutToShowBouncerProgress(expansion));
} else {
- viewState.setAlpha(ShadeInterpolation.getContentAlpha(expansion));
+ FeatureFlags flags = ambientState.getFeatureFlags();
+ if (ambientState.isSmallScreen() || !flags.isEnabled(
+ Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ viewState.setAlpha(ShadeInterpolation.getContentAlpha(expansion));
+ } else {
+ LargeScreenShadeInterpolator interpolator =
+ ambientState.getLargeScreenShadeInterpolator();
+ viewState.setAlpha(interpolator.getNotificationContentAlpha(expansion));
+ }
}
} else {
viewState.setAlpha(1f - ambientState.getHideAmount());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index c35c5c5..7755003 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -93,6 +93,16 @@
@IntDef({STATE_ICON, STATE_DOT, STATE_HIDDEN})
public @interface VisibleState { }
+ /** Returns a human-readable string of {@link VisibleState}. */
+ public static String getVisibleStateString(@VisibleState int state) {
+ switch(state) {
+ case STATE_ICON: return "ICON";
+ case STATE_DOT: return "DOT";
+ case STATE_HIDDEN: return "HIDDEN";
+ default: return "UNKNOWN";
+ }
+ }
+
private static final String TAG = "StatusBarIconView";
private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
= new FloatProperty<StatusBarIconView>("iconAppearAmount") {
@@ -561,7 +571,8 @@
@Override
public String toString() {
return "StatusBarIconView("
- + "slot='" + mSlot + " alpha=" + getAlpha() + " icon=" + mIcon
+ + "slot='" + mSlot + "' alpha=" + getAlpha() + " icon=" + mIcon
+ + " visibleState=" + getVisibleStateString(getVisibleState())
+ " iconColor=#" + Integer.toHexString(mIconColor)
+ " notification=" + mNotification + ')';
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 82c5ee6..826e289 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -501,8 +501,10 @@
private fun updateTextColorFromRegionSampler() {
smartspaceViews.forEach {
- val textColor = regionSamplers.getValue(it).currentForegroundColor()
- it.setPrimaryTextColor(textColor)
+ val textColor = regionSamplers.get(it)?.currentForegroundColor()
+ if (textColor != null) {
+ it.setPrimaryTextColor(textColor)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt
new file mode 100644
index 0000000..5ce1db2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 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.collection.coordinator
+
+import android.util.ArrayMap
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
+import com.android.systemui.statusbar.notification.collection.render.NotifGroupController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlin.math.max
+import kotlin.math.min
+
+/** A small coordinator which finds, stores, and applies the closest notification time. */
+@CoordinatorScope
+class GroupWhenCoordinator
+@Inject
+constructor(
+ @Main private val delayableExecutor: DelayableExecutor,
+ private val systemClock: SystemClock
+) : Coordinator {
+
+ private val invalidator = object : Invalidator("GroupWhenCoordinator") {}
+ private val notificationGroupTimes = ArrayMap<GroupEntry, Long>()
+ private var cancelInvalidateListRunnable: Runnable? = null
+
+ private val invalidateListRunnable: Runnable = Runnable {
+ invalidator.invalidateList("future notification invalidation")
+ }
+
+ override fun attach(pipeline: NotifPipeline) {
+ pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilterListener)
+ pipeline.addOnAfterRenderGroupListener(::onAfterRenderGroupListener)
+ pipeline.addPreRenderInvalidator(invalidator)
+ }
+
+ private fun onBeforeFinalizeFilterListener(entries: List<ListEntry>) {
+ cancelListInvalidation()
+ notificationGroupTimes.clear()
+
+ val now = systemClock.currentTimeMillis()
+ var closestFutureTime = Long.MAX_VALUE
+ entries.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry ->
+ val whenMillis = calculateGroupNotificationTime(groupEntry, now)
+ notificationGroupTimes[groupEntry] = whenMillis
+ if (whenMillis > now) {
+ closestFutureTime = min(closestFutureTime, whenMillis)
+ }
+ }
+
+ if (closestFutureTime != Long.MAX_VALUE) {
+ cancelInvalidateListRunnable =
+ delayableExecutor.executeDelayed(invalidateListRunnable, closestFutureTime - now)
+ }
+ }
+
+ private fun cancelListInvalidation() {
+ cancelInvalidateListRunnable?.run()
+ cancelInvalidateListRunnable = null
+ }
+
+ private fun onAfterRenderGroupListener(group: GroupEntry, controller: NotifGroupController) {
+ notificationGroupTimes[group]?.let(controller::setNotificationGroupWhen)
+ }
+
+ private fun calculateGroupNotificationTime(
+ groupEntry: GroupEntry,
+ currentTimeMillis: Long
+ ): Long {
+ var pastTime = Long.MIN_VALUE
+ var futureTime = Long.MAX_VALUE
+ groupEntry.children
+ .asSequence()
+ .mapNotNull { child -> child.sbn.notification.`when`.takeIf { it > 0 } }
+ .forEach { time ->
+ val isInThePast = currentTimeMillis - time > 0
+ if (isInThePast) {
+ pastTime = max(pastTime, time)
+ } else {
+ futureTime = min(futureTime, time)
+ }
+ }
+
+ if (pastTime == Long.MIN_VALUE && futureTime == Long.MAX_VALUE) {
+ return checkNotNull(groupEntry.summary).creationTime
+ }
+
+ return if (futureTime != Long.MAX_VALUE) futureTime else pastTime
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 8a82bca..6bb5b92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -31,31 +31,32 @@
@CoordinatorScope
class NotifCoordinatorsImpl @Inject constructor(
- notifPipelineFlags: NotifPipelineFlags,
- dataStoreCoordinator: DataStoreCoordinator,
- hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
- hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
- keyguardCoordinator: KeyguardCoordinator,
- rankingCoordinator: RankingCoordinator,
- appOpsCoordinator: AppOpsCoordinator,
- deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
- bubbleCoordinator: BubbleCoordinator,
- headsUpCoordinator: HeadsUpCoordinator,
- gutsCoordinator: GutsCoordinator,
- conversationCoordinator: ConversationCoordinator,
- debugModeCoordinator: DebugModeCoordinator,
- groupCountCoordinator: GroupCountCoordinator,
- mediaCoordinator: MediaCoordinator,
- preparationCoordinator: PreparationCoordinator,
- remoteInputCoordinator: RemoteInputCoordinator,
- rowAppearanceCoordinator: RowAppearanceCoordinator,
- stackCoordinator: StackCoordinator,
- shadeEventCoordinator: ShadeEventCoordinator,
- smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
- viewConfigCoordinator: ViewConfigCoordinator,
- visualStabilityCoordinator: VisualStabilityCoordinator,
- sensitiveContentCoordinator: SensitiveContentCoordinator,
- dismissibilityCoordinator: DismissibilityCoordinator
+ notifPipelineFlags: NotifPipelineFlags,
+ dataStoreCoordinator: DataStoreCoordinator,
+ hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
+ hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
+ keyguardCoordinator: KeyguardCoordinator,
+ rankingCoordinator: RankingCoordinator,
+ appOpsCoordinator: AppOpsCoordinator,
+ deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
+ bubbleCoordinator: BubbleCoordinator,
+ headsUpCoordinator: HeadsUpCoordinator,
+ gutsCoordinator: GutsCoordinator,
+ conversationCoordinator: ConversationCoordinator,
+ debugModeCoordinator: DebugModeCoordinator,
+ groupCountCoordinator: GroupCountCoordinator,
+ groupWhenCoordinator: GroupWhenCoordinator,
+ mediaCoordinator: MediaCoordinator,
+ preparationCoordinator: PreparationCoordinator,
+ remoteInputCoordinator: RemoteInputCoordinator,
+ rowAppearanceCoordinator: RowAppearanceCoordinator,
+ stackCoordinator: StackCoordinator,
+ shadeEventCoordinator: ShadeEventCoordinator,
+ smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
+ viewConfigCoordinator: ViewConfigCoordinator,
+ visualStabilityCoordinator: VisualStabilityCoordinator,
+ sensitiveContentCoordinator: SensitiveContentCoordinator,
+ dismissibilityCoordinator: DismissibilityCoordinator
) : NotifCoordinators {
private val mCoordinators: MutableList<Coordinator> = ArrayList()
@@ -82,6 +83,7 @@
mCoordinators.add(debugModeCoordinator)
mCoordinators.add(conversationCoordinator)
mCoordinators.add(groupCountCoordinator)
+ mCoordinators.add(groupWhenCoordinator)
mCoordinators.add(mediaCoordinator)
mCoordinators.add(rowAppearanceCoordinator)
mCoordinators.add(stackCoordinator)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt
index e2edc01..061ef9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt
@@ -20,4 +20,7 @@
interface NotifGroupController {
/** Set the number of children that this group would have if not for the 8-child max */
fun setUntruncatedChildCount(untruncatedChildCount: Int)
+
+ /** Set the when value of notification group that reflects most important closest notification time */
+ fun setNotificationGroupWhen(whenMillis: Long)
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 2affa77..68552d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -153,7 +153,6 @@
// We don't correctly track dark mode until the content views are inflated, so always update
// the background on first content update just in case it happens to be during a theme change.
private boolean mUpdateSelfBackgroundOnUpdate = true;
- private boolean mNotificationTranslationFinished = false;
private boolean mIsSnoozed;
private boolean mIsFaded;
private boolean mAnimatePinnedRoundness = false;
@@ -192,7 +191,6 @@
private int mMaxSmallHeightBeforeS;
private int mMaxSmallHeight;
private int mMaxSmallHeightLarge;
- private int mMaxSmallHeightMedia;
private int mMaxExpandedHeight;
private int mIncreasedPaddingBetweenElements;
private int mNotificationLaunchHeight;
@@ -211,11 +209,6 @@
*/
private boolean mUserExpanded;
/**
- * Whether the blocking helper is showing on this notification (even if dismissed)
- */
- private boolean mIsBlockingHelperShowing;
-
- /**
* Has this notification been expanded while it was pinned
*/
private boolean mExpandedWhenPinned;
@@ -853,6 +846,19 @@
}
/**
+ * @see NotificationChildrenContainer#setNotificationGroupWhen(long)
+ */
+ public void setNotificationGroupWhen(long whenMillis) {
+ if (mIsSummaryWithChildren) {
+ mChildrenContainer.setNotificationGroupWhen(whenMillis);
+ } else {
+ Log.w(TAG, "setNotificationGroupWhen( whenMillis: " + whenMillis + ")"
+ + " mIsSummaryWithChildren: false"
+ + " mChildrenContainer has not been inflated yet.");
+ }
+ }
+
+ /**
* Called after children have been attached to set the expansion states
*/
public void resetChildSystemExpandedStates() {
@@ -1567,18 +1573,6 @@
}
}
- public void setBlockingHelperShowing(boolean isBlockingHelperShowing) {
- mIsBlockingHelperShowing = isBlockingHelperShowing;
- }
-
- public boolean isBlockingHelperShowing() {
- return mIsBlockingHelperShowing;
- }
-
- public boolean isBlockingHelperShowingAndTranslationFinished() {
- return mIsBlockingHelperShowing && mNotificationTranslationFinished;
- }
-
@Override
public View getShelfTransformationTarget() {
if (mIsSummaryWithChildren && !shouldShowPublic()) {
@@ -1774,8 +1768,6 @@
R.dimen.notification_min_height);
mMaxSmallHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height_increased);
- mMaxSmallHeightMedia = NotificationUtils.getFontScaledHeight(mContext,
- R.dimen.notification_min_height_media);
mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_height);
mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext,
@@ -2161,10 +2153,7 @@
@Override
public void setTranslation(float translationX) {
invalidate();
- if (isBlockingHelperShowingAndTranslationFinished()) {
- mGuts.setTranslationX(translationX);
- return;
- } else if (mDismissUsingRowTranslationX) {
+ if (mDismissUsingRowTranslationX) {
setTranslationX(translationX);
} else if (mTranslateableViews != null) {
// Translate the group of views
@@ -2192,10 +2181,6 @@
return getTranslationX();
}
- if (isBlockingHelperShowingAndCanTranslate()) {
- return mGuts.getTranslationX();
- }
-
if (mTranslateableViews != null && mTranslateableViews.size() > 0) {
// All of the views in the list should have same translation, just use first one.
return mTranslateableViews.get(0).getTranslationX();
@@ -2204,10 +2189,6 @@
return 0;
}
- private boolean isBlockingHelperShowingAndCanTranslate() {
- return areGutsExposed() && mIsBlockingHelperShowing && mNotificationTranslationFinished;
- }
-
public Animator getTranslateViewAnimator(final float leftTarget,
AnimatorUpdateListener listener) {
if (mTranslateAnim != null) {
@@ -2229,9 +2210,6 @@
@Override
public void onAnimationEnd(Animator anim) {
- if (mIsBlockingHelperShowing) {
- mNotificationTranslationFinished = true;
- }
if (!cancelled && leftTarget == 0) {
if (mMenuRow != null) {
mMenuRow.resetMenu();
@@ -2811,9 +2789,10 @@
int intrinsicBefore = getIntrinsicHeight();
mSensitive = sensitive;
mSensitiveHiddenInGeneral = hideSensitive;
- if (intrinsicBefore != getIntrinsicHeight()) {
- // The animation has a few flaws and is highly visible, so jump cut instead.
- notifyHeightChanged(false /* needsAnimation */);
+ int intrinsicAfter = getIntrinsicHeight();
+ if (intrinsicBefore != intrinsicAfter) {
+ boolean needsAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
+ notifyHeightChanged(needsAnimation);
}
}
@@ -2870,13 +2849,19 @@
View[] publicViews = new View[]{mPublicLayout};
View[] hiddenChildren = showingPublic ? privateViews : publicViews;
View[] shownChildren = showingPublic ? publicViews : privateViews;
+ // disappear/appear overlap: 10 percent of duration
+ long overlap = duration / 10;
+ // disappear duration: 1/3 of duration + half of overlap
+ long disappearDuration = duration / 3 + overlap / 2;
+ // appear duration: 2/3 of duration + half of overlap
+ long appearDuration = (duration - disappearDuration) + overlap / 2;
for (final View hiddenView : hiddenChildren) {
hiddenView.setVisibility(View.VISIBLE);
hiddenView.animate().cancel();
hiddenView.animate()
.alpha(0f)
.setStartDelay(delay)
- .setDuration(duration)
+ .setDuration(disappearDuration)
.withEndAction(() -> {
hiddenView.setVisibility(View.INVISIBLE);
resetAllContentAlphas();
@@ -2888,8 +2873,8 @@
showView.animate().cancel();
showView.animate()
.alpha(1f)
- .setStartDelay(delay)
- .setDuration(duration);
+ .setStartDelay(delay + duration - appearDuration)
+ .setDuration(appearDuration);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 2dda6fd..dfc80fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -349,6 +349,15 @@
}
@Override
+ public void setNotificationGroupWhen(long whenMillis) {
+ if (mView.isSummaryWithChildren()) {
+ mView.setNotificationGroupWhen(whenMillis);
+ } else {
+ Log.w(TAG, "Called setNotificationTime(" + whenMillis + ") on a leaf row");
+ }
+ }
+
+ @Override
public void setSystemExpanded(boolean systemExpanded) {
mView.setSystemExpanded(systemExpanded);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 39e4000..4522e41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -443,6 +443,7 @@
CancellationSignal cancellationSignal = new CancellationSignal();
cancellationSignal.setOnCancelListener(
() -> runningInflations.values().forEach(CancellationSignal::cancel));
+
return cancellationSignal;
}
@@ -783,6 +784,7 @@
public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>
implements InflationCallback, InflationTask {
+ private static final long IMG_PRELOAD_TIMEOUT_MS = 1000L;
private final NotificationEntry mEntry;
private final Context mContext;
private final boolean mInflateSynchronously;
@@ -876,7 +878,7 @@
recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
mUsesIncreasedHeadsUpHeight, packageContext);
InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
- return inflateSmartReplyViews(
+ InflationProgress result = inflateSmartReplyViews(
inflationProgress,
mReInflateFlags,
mEntry,
@@ -884,6 +886,11 @@
packageContext,
previousSmartReplyState,
mSmartRepliesInflater);
+
+ // wait for image resolver to finish preloading
+ mRow.getImageResolver().waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS);
+
+ return result;
} catch (Exception e) {
mError = e;
return null;
@@ -918,6 +925,9 @@
mCallback.handleInflationException(mRow.getEntry(),
new InflationException("Couldn't inflate contentViews" + e));
}
+
+ // Cancel any image loading tasks, not useful any more
+ mRow.getImageResolver().cancelRunningTasks();
}
@Override
@@ -944,6 +954,9 @@
// Notify the resolver that the inflation task has finished,
// try to purge unnecessary cached entries.
mRow.getImageResolver().purgeCache();
+
+ // Cancel any image loading tasks that have not completed at this point
+ mRow.getImageResolver().cancelRunningTasks();
}
private static class RtlEnabledContext extends ContextWrapper {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
index 93f0812..596bdc0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
@@ -238,12 +238,11 @@
}
public void openControls(
- boolean shouldDoCircularReveal,
int x,
int y,
boolean needsFalsingProtection,
@Nullable Runnable onAnimationEnd) {
- animateOpen(shouldDoCircularReveal, x, y, onAnimationEnd);
+ animateOpen(x, y, onAnimationEnd);
setExposed(true /* exposed */, needsFalsingProtection);
}
@@ -300,7 +299,7 @@
if (mGutsContent == null
|| !mGutsContent.handleCloseControls(save, force)) {
// We only want to do a circular reveal if we're not showing the blocking helper.
- animateClose(x, y, true /* shouldDoCircularReveal */);
+ animateClose(x, y);
setExposed(false, mNeedsFalsingProtection);
if (mClosedListener != null) {
@@ -309,66 +308,45 @@
}
}
- /** Animates in the guts view via either a fade or a circular reveal. */
- private void animateOpen(
- boolean shouldDoCircularReveal, int x, int y, @Nullable Runnable onAnimationEnd) {
+ /** Animates in the guts view with a circular reveal. */
+ private void animateOpen(int x, int y, @Nullable Runnable onAnimationEnd) {
if (isAttachedToWindow()) {
- if (shouldDoCircularReveal) {
- double horz = Math.max(getWidth() - x, x);
- double vert = Math.max(getHeight() - y, y);
- float r = (float) Math.hypot(horz, vert);
- // Make sure we'll be visible after the circular reveal
- setAlpha(1f);
- // Circular reveal originating at (x, y)
- Animator a = ViewAnimationUtils.createCircularReveal(this, x, y, 0, r);
- a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- a.addListener(new AnimateOpenListener(onAnimationEnd));
- a.start();
- } else {
- // Fade in content
- this.setAlpha(0f);
- this.animate()
- .alpha(1f)
- .setDuration(StackStateAnimator.ANIMATION_DURATION_BLOCKING_HELPER_FADE)
- .setInterpolator(Interpolators.ALPHA_IN)
- .setListener(new AnimateOpenListener(onAnimationEnd))
- .start();
- }
+ double horz = Math.max(getWidth() - x, x);
+ double vert = Math.max(getHeight() - y, y);
+ float r = (float) Math.hypot(horz, vert);
+ // Make sure we'll be visible after the circular reveal
+ setAlpha(1f);
+ // Circular reveal originating at (x, y)
+ Animator a = ViewAnimationUtils.createCircularReveal(this, x, y, 0, r);
+ a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ a.addListener(new AnimateOpenListener(onAnimationEnd));
+ a.start();
+
} else {
Log.w(TAG, "Failed to animate guts open");
}
}
- /** Animates out the guts view via either a fade or a circular reveal. */
+ /** Animates out the guts view with a circular reveal. */
@VisibleForTesting
- void animateClose(int x, int y, boolean shouldDoCircularReveal) {
+ void animateClose(int x, int y) {
if (isAttachedToWindow()) {
- if (shouldDoCircularReveal) {
- // Circular reveal originating at (x, y)
- if (x == -1 || y == -1) {
- x = (getLeft() + getRight()) / 2;
- y = (getTop() + getHeight() / 2);
- }
- double horz = Math.max(getWidth() - x, x);
- double vert = Math.max(getHeight() - y, y);
- float r = (float) Math.hypot(horz, vert);
- Animator a = ViewAnimationUtils.createCircularReveal(this,
- x, y, r, 0);
- a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- a.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
- a.addListener(new AnimateCloseListener(this /* view */, mGutsContent));
- a.start();
- } else {
- // Fade in the blocking helper.
- this.animate()
- .alpha(0f)
- .setDuration(StackStateAnimator.ANIMATION_DURATION_BLOCKING_HELPER_FADE)
- .setInterpolator(Interpolators.ALPHA_OUT)
- .setListener(new AnimateCloseListener(this, /* view */mGutsContent))
- .start();
+ // Circular reveal originating at (x, y)
+ if (x == -1 || y == -1) {
+ x = (getLeft() + getRight()) / 2;
+ y = (getTop() + getHeight() / 2);
}
+ double horz = Math.max(getWidth() - x, x);
+ double vert = Math.max(getHeight() - y, y);
+ float r = (float) Math.hypot(horz, vert);
+ Animator a = ViewAnimationUtils.createCircularReveal(this,
+ x, y, r, 0);
+ a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ a.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+ a.addListener(new AnimateCloseListener(this /* view */, mGutsContent));
+ a.start();
} else {
Log.w(TAG, "Failed to animate guts close");
mGutsContent.onFinishedClosing();
@@ -449,7 +427,7 @@
return mGutsContent != null && mGutsContent.isLeavebehind();
}
- /** Listener for animations executed in {@link #animateOpen(boolean, int, int, Runnable)}. */
+ /** Listener for animations executed in {@link #animateOpen(int, int, Runnable)}. */
private static class AnimateOpenListener extends AnimatorListenerAdapter {
final Runnable mOnAnimationEnd;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 06d4080..46f1bb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -629,7 +629,6 @@
!mAccessibilityManager.isTouchExplorationEnabled());
guts.openControls(
- !row.isBlockingHelperShowing(),
x,
y,
needsFalsingProtection,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java
index 41eeada0..fe0b312 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java
@@ -22,8 +22,11 @@
import android.util.Log;
import java.util.Set;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* A cache for inline images of image messages.
@@ -56,12 +59,13 @@
}
@Override
- public Drawable get(Uri uri) {
+ public Drawable get(Uri uri, long timeoutMs) {
Drawable result = null;
try {
- result = mCache.get(uri).get();
- } catch (InterruptedException | ExecutionException ex) {
- Log.d(TAG, "get: Failed get image from " + uri);
+ result = mCache.get(uri).get(timeoutMs, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException | ExecutionException
+ | TimeoutException | CancellationException ex) {
+ Log.d(TAG, "get: Failed get image from " + uri + " " + ex);
}
return result;
}
@@ -72,6 +76,15 @@
mCache.entrySet().removeIf(entry -> !wantedSet.contains(entry.getKey()));
}
+ @Override
+ public void cancelRunningTasks() {
+ mCache.forEach((key, value) -> {
+ if (value.getStatus() != AsyncTask.Status.FINISHED) {
+ value.cancel(true);
+ }
+ });
+ }
+
private static class PreloadImageTask extends AsyncTask<Uri, Void, Drawable> {
private final NotificationInlineImageResolver mResolver;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
index b05e64ab..c620f44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
@@ -23,6 +23,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
+import android.os.SystemClock;
import android.util.Log;
import com.android.internal.R;
@@ -45,6 +46,9 @@
public class NotificationInlineImageResolver implements ImageResolver {
private static final String TAG = NotificationInlineImageResolver.class.getSimpleName();
+ // Timeout for loading images from ImageCache when calling from UI thread
+ private static final long MAX_UI_THREAD_TIMEOUT_MS = 100L;
+
private final Context mContext;
private final ImageCache mImageCache;
private Set<Uri> mWantedUriSet;
@@ -123,17 +127,25 @@
return null;
}
+ /**
+ * Loads an image from the Uri.
+ * This method is synchronous and is usually called from the Main thread.
+ * It will time-out after MAX_UI_THREAD_TIMEOUT_MS.
+ *
+ * @param uri Uri of the target image.
+ * @return drawable of the image, null if loading failed/timeout
+ */
@Override
public Drawable loadImage(Uri uri) {
- return hasCache() ? loadImageFromCache(uri) : resolveImage(uri);
+ return hasCache() ? loadImageFromCache(uri, MAX_UI_THREAD_TIMEOUT_MS) : resolveImage(uri);
}
- private Drawable loadImageFromCache(Uri uri) {
+ private Drawable loadImageFromCache(Uri uri, long timeoutMs) {
// if the uri isn't currently cached, try caching it first
if (!mImageCache.hasEntry(uri)) {
mImageCache.preload((uri));
}
- return mImageCache.get(uri);
+ return mImageCache.get(uri, timeoutMs);
}
/**
@@ -208,6 +220,30 @@
}
/**
+ * Wait for a maximum timeout for images to finish preloading
+ * @param timeoutMs total timeout time
+ */
+ void waitForPreloadedImages(long timeoutMs) {
+ if (!hasCache()) {
+ return;
+ }
+ Set<Uri> preloadedUris = getWantedUriSet();
+ if (preloadedUris != null) {
+ // Decrement remaining timeout after each image check
+ long endTimeMs = SystemClock.elapsedRealtime() + timeoutMs;
+ preloadedUris.forEach(
+ uri -> loadImageFromCache(uri, endTimeMs - SystemClock.elapsedRealtime()));
+ }
+ }
+
+ void cancelRunningTasks() {
+ if (!hasCache()) {
+ return;
+ }
+ mImageCache.cancelRunningTasks();
+ }
+
+ /**
* A interface for internal cache implementation of this resolver.
*/
interface ImageCache {
@@ -216,7 +252,7 @@
* @param uri The uri of the image.
* @return Drawable of the image.
*/
- Drawable get(Uri uri);
+ Drawable get(Uri uri, long timeoutMs);
/**
* Set the image resolver that actually resolves image from specified uri.
@@ -241,6 +277,11 @@
* Purge unnecessary entries in the cache.
*/
void purge();
+
+ /**
+ * Cancel all unfinished image loading tasks
+ */
+ void cancelRunningTasks();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 1f664cb..9a777ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -27,6 +27,7 @@
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import android.widget.DateTimeView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
@@ -344,6 +345,21 @@
mTransformationHelper.setVisible(visible);
}
+ /***
+ * Set Notification when value
+ * @param whenMillis
+ */
+ public void setNotificationWhen(long whenMillis) {
+ if (mNotificationHeader == null) {
+ return;
+ }
+
+ final View timeView = mNotificationHeader.findViewById(com.android.internal.R.id.time);
+
+ if (timeView instanceof DateTimeView) {
+ ((DateTimeView) timeView).setTime(whenMillis);
+ }
+ }
protected void addTransformedViews(View... views) {
for (View view : views) {
if (view != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 6f4d6d9..77ede04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -29,6 +29,8 @@
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -55,6 +57,8 @@
private final SectionProvider mSectionProvider;
private final BypassController mBypassController;
+ private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+ private final FeatureFlags mFeatureFlags;
/**
* Used to read bouncer states.
*/
@@ -84,7 +88,7 @@
private float mExpandingVelocity;
private boolean mPanelTracking;
private boolean mExpansionChanging;
- private boolean mPanelFullWidth;
+ private boolean mIsSmallScreen;
private boolean mPulsing;
private boolean mUnlockHintRunning;
private float mHideAmount;
@@ -252,10 +256,14 @@
@NonNull DumpManager dumpManager,
@NonNull SectionProvider sectionProvider,
@NonNull BypassController bypassController,
- @Nullable StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ @Nullable StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ @NonNull LargeScreenShadeInterpolator largeScreenShadeInterpolator,
+ @NonNull FeatureFlags featureFlags) {
mSectionProvider = sectionProvider;
mBypassController = bypassController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
+ mFeatureFlags = featureFlags;
reload(context);
dumpManager.registerDumpable(this);
}
@@ -574,12 +582,12 @@
return mPanelTracking;
}
- public boolean isPanelFullWidth() {
- return mPanelFullWidth;
+ public boolean isSmallScreen() {
+ return mIsSmallScreen;
}
- public void setPanelFullWidth(boolean panelFullWidth) {
- mPanelFullWidth = panelFullWidth;
+ public void setSmallScreen(boolean smallScreen) {
+ mIsSmallScreen = smallScreen;
}
public void setUnlockHintRunning(boolean unlockHintRunning) {
@@ -736,6 +744,14 @@
return mIsClosing;
}
+ public LargeScreenShadeInterpolator getLargeScreenShadeInterpolator() {
+ return mLargeScreenShadeInterpolator;
+ }
+
+ public FeatureFlags getFeatureFlags() {
+ return mFeatureFlags;
+ }
+
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("mTopPadding=" + mTopPadding);
@@ -751,7 +767,7 @@
pw.println("mDimmed=" + mDimmed);
pw.println("mStatusBarState=" + mStatusBarState);
pw.println("mExpansionChanging=" + mExpansionChanging);
- pw.println("mPanelFullWidth=" + mPanelFullWidth);
+ pw.println("mPanelFullWidth=" + mIsSmallScreen);
pw.println("mPulsing=" + mPulsing);
pw.println("mPulseHeight=" + mPulseHeight);
pw.println("mTrackedHeadsUpRow.key=" + logKey(mTrackedHeadsUpRow));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 9b93d7b..40f55bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -296,6 +296,19 @@
}
/**
+ * Set the notification time in the group so that the view can show the latest event in the UI
+ * appropriately.
+ */
+ public void setNotificationGroupWhen(long whenMillis) {
+ if (mNotificationHeaderWrapper != null) {
+ mNotificationHeaderWrapper.setNotificationWhen(whenMillis);
+ }
+ if (mNotificationHeaderWrapperLowPriority != null) {
+ mNotificationHeaderWrapperLowPriority.setNotificationWhen(whenMillis);
+ }
+ }
+
+ /**
* Add a child notification to this view.
*
* @param row the row to add
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index e09b94b..e2e2a23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -67,6 +67,7 @@
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AnimationUtils;
@@ -199,6 +200,7 @@
private final boolean mDebugRemoveAnimation;
private final boolean mSimplifiedAppearFraction;
private final boolean mUseRoundnessSourceTypes;
+ private boolean mAnimatedInsets;
private int mContentHeight;
private float mIntrinsicContentHeight;
@@ -207,7 +209,8 @@
private int mTopPadding;
private boolean mAnimateNextTopPaddingChange;
private int mBottomPadding;
- private int mBottomInset = 0;
+ @VisibleForTesting
+ int mBottomInset = 0;
private float mQsExpansionFraction;
private final int mSplitShadeMinContentHeight;
@@ -388,9 +391,33 @@
}
}
};
+
private boolean mPulsing;
private boolean mScrollable;
private View mForcedScroll;
+ private boolean mIsInsetAnimationRunning;
+
+ private final WindowInsetsAnimation.Callback mInsetsCallback =
+ new WindowInsetsAnimation.Callback(
+ WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
+
+ @Override
+ public void onPrepare(WindowInsetsAnimation animation) {
+ mIsInsetAnimationRunning = true;
+ }
+
+ @Override
+ public WindowInsets onProgress(WindowInsets windowInsets,
+ List<WindowInsetsAnimation> list) {
+ updateBottomInset(windowInsets);
+ return windowInsets;
+ }
+
+ @Override
+ public void onEnd(WindowInsetsAnimation animation) {
+ mIsInsetAnimationRunning = false;
+ }
+ };
/**
* @see #setHideAmount(float, float)
@@ -584,6 +611,7 @@
mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
mSimplifiedAppearFraction = featureFlags.isEnabled(Flags.SIMPLIFIED_APPEAR_FRACTION);
mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
+ setAnimatedInsetsEnabled(featureFlags.isEnabled(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS));
mSectionsManager = Dependency.get(NotificationSectionsManager.class);
mScreenOffAnimationController =
Dependency.get(ScreenOffAnimationController.class);
@@ -622,6 +650,9 @@
mGroupMembershipManager = Dependency.get(GroupMembershipManager.class);
mGroupExpansionManager = Dependency.get(GroupExpansionManager.class);
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ if (mAnimatedInsets) {
+ setWindowInsetsAnimationCallback(mInsetsCallback);
+ }
}
/**
@@ -690,6 +721,11 @@
}
@VisibleForTesting
+ void setAnimatedInsetsEnabled(boolean enabled) {
+ mAnimatedInsets = enabled;
+ }
+
+ @VisibleForTesting
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void updateFooter() {
if (mFooterView == null) {
@@ -1781,7 +1817,11 @@
return;
}
mForcedScroll = v;
- scrollTo(v);
+ if (mAnimatedInsets) {
+ updateForcedScroll();
+ } else {
+ scrollTo(v);
+ }
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -1813,26 +1853,46 @@
+ ((!isExpanded() && isPinnedHeadsUp(v)) ? mHeadsUpInset : getTopPadding());
}
+ private void updateBottomInset(WindowInsets windowInsets) {
+ mBottomInset = windowInsets.getInsets(WindowInsets.Type.ime()).bottom;
+
+ if (mForcedScroll != null) {
+ updateForcedScroll();
+ }
+
+ int range = getScrollRange();
+ if (mOwnScrollY > range) {
+ setOwnScrollY(range);
+ }
+ }
+
@Override
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom;
+ if (!mAnimatedInsets) {
+ mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom;
+ }
mWaterfallTopInset = 0;
final DisplayCutout cutout = insets.getDisplayCutout();
if (cutout != null) {
mWaterfallTopInset = cutout.getWaterfallInsets().top;
}
-
- int range = getScrollRange();
- if (mOwnScrollY > range) {
- // HACK: We're repeatedly getting staggered insets here while the IME is
- // animating away. To work around that we'll wait until things have settled.
- removeCallbacks(mReclamp);
- postDelayed(mReclamp, 50);
- } else if (mForcedScroll != null) {
- // The scroll was requested before we got the actual inset - in case we need
- // to scroll up some more do so now.
- scrollTo(mForcedScroll);
+ if (mAnimatedInsets && !mIsInsetAnimationRunning) {
+ // update bottom inset e.g. after rotation
+ updateBottomInset(insets);
+ }
+ if (!mAnimatedInsets) {
+ int range = getScrollRange();
+ if (mOwnScrollY > range) {
+ // HACK: We're repeatedly getting staggered insets here while the IME is
+ // animating away. To work around that we'll wait until things have settled.
+ removeCallbacks(mReclamp);
+ postDelayed(mReclamp, 50);
+ } else if (mForcedScroll != null) {
+ // The scroll was requested before we got the actual inset - in case we need
+ // to scroll up some more do so now.
+ scrollTo(mForcedScroll);
+ }
}
return insets;
}
@@ -5163,7 +5223,7 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void setIsFullWidth(boolean isFullWidth) {
- mAmbientState.setPanelFullWidth(isFullWidth);
+ mAmbientState.setSmallScreen(isFullWidth);
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 92c5b63..e8058b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -70,7 +70,6 @@
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -172,7 +171,6 @@
private final CentralSurfaces mCentralSurfaces;
private final SectionHeaderController mSilentHeaderController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
- private final ShadeTransitionController mShadeTransitionController;
private final InteractionJankMonitor mJankMonitor;
private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private final StackStateLogger mStackStateLogger;
@@ -664,7 +662,6 @@
NotifPipelineFlags notifPipelineFlags,
NotifCollection notifCollection,
LockscreenShadeTransitionController lockscreenShadeTransitionController,
- ShadeTransitionController shadeTransitionController,
UiEventLogger uiEventLogger,
NotificationRemoteInputManager remoteInputManager,
VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
@@ -697,7 +694,6 @@
mMetricsLogger = metricsLogger;
mDumpManager = dumpManager;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
- mShadeTransitionController = shadeTransitionController;
mFalsingCollector = falsingCollector;
mFalsingManager = falsingManager;
mResources = resources;
@@ -781,7 +777,6 @@
mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming);
mLockscreenShadeTransitionController.setStackScroller(this);
- mShadeTransitionController.setNotificationStackScrollLayoutController(this);
mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index d2bff0e..8ef28ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -29,6 +29,9 @@
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.R;
import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.LegacySourceType;
@@ -135,7 +138,6 @@
AmbientState ambientState) {
for (ExpandableView view : algorithmState.visibleChildren) {
final ViewState viewState = view.getViewState();
-
final boolean isHunGoingToShade = ambientState.isShadeExpanded()
&& view == ambientState.getTrackedHeadsUpRow();
@@ -148,9 +150,14 @@
} else if (ambientState.isExpansionChanging()) {
// Adjust alpha for shade open & close.
float expansion = ambientState.getExpansionFraction();
- viewState.setAlpha(ambientState.isBouncerInTransit()
- ? BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansion)
- : ShadeInterpolation.getContentAlpha(expansion));
+ if (ambientState.isBouncerInTransit()) {
+ viewState.setAlpha(
+ BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansion));
+ } else if (view instanceof FooterView) {
+ viewState.setAlpha(interpolateFooterAlpha(ambientState));
+ } else {
+ viewState.setAlpha(interpolateNotificationContentAlpha(ambientState));
+ }
}
// For EmptyShadeView if on keyguard, we need to control the alpha to create
@@ -182,6 +189,28 @@
}
}
+ private float interpolateFooterAlpha(AmbientState ambientState) {
+ float expansion = ambientState.getExpansionFraction();
+ FeatureFlags flags = ambientState.getFeatureFlags();
+ if (ambientState.isSmallScreen()
+ || !flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ return ShadeInterpolation.getContentAlpha(expansion);
+ }
+ LargeScreenShadeInterpolator interpolator = ambientState.getLargeScreenShadeInterpolator();
+ return interpolator.getNotificationFooterAlpha(expansion);
+ }
+
+ private float interpolateNotificationContentAlpha(AmbientState ambientState) {
+ float expansion = ambientState.getExpansionFraction();
+ FeatureFlags flags = ambientState.getFeatureFlags();
+ if (ambientState.isSmallScreen()
+ || !flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ return ShadeInterpolation.getContentAlpha(expansion);
+ }
+ LargeScreenShadeInterpolator interpolator = ambientState.getLargeScreenShadeInterpolator();
+ return interpolator.getNotificationContentAlpha(expansion);
+ }
+
/**
* How expanded or collapsed notifications are when pulling down the shade.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 9e62817..55fa479 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -122,7 +122,6 @@
options.setLaunchDisplayId(displayId);
options.setCallerDisplayId(displayId);
options.setPendingIntentBackgroundActivityLaunchAllowed(true);
- options.setInteractive(true);
return options.toBundle();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index c72eb05..39b5b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -40,6 +40,7 @@
import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger;
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
@@ -288,10 +289,14 @@
* @param mobileContext possibly mcc/mnc overridden mobile context
* @param subId the subscriptionId for this mobile view
*/
- public void addModernMobileView(Context mobileContext, int subId) {
+ public void addModernMobileView(
+ Context mobileContext,
+ MobileViewLogger mobileViewLogger,
+ int subId) {
Log.d(TAG, "addModernMobileView (subId=" + subId + ")");
ModernStatusBarMobileView view = ModernStatusBarMobileView.constructAndBind(
mobileContext,
+ mobileViewLogger,
"mobile",
mMobileIconsViewModel.viewModelForSub(subId, mLocation)
);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 9fb942c..0bded73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -54,15 +54,18 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
+import com.android.systemui.keyguard.shared.model.ScrimAlpha;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.notification.stack.ViewState;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -206,7 +209,6 @@
private final ScreenOffAnimationController mScreenOffAnimationController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private final SysuiStatusBarStateController mStatusBarStateController;
private GradientColors mColors;
private boolean mNeedsDrawableColorUpdate;
@@ -245,6 +247,8 @@
private boolean mWallpaperVisibilityTimedOut;
private int mScrimsVisibility;
private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
+ private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+ private final FeatureFlags mFeatureFlags;
private Consumer<Integer> mScrimVisibleListener;
private boolean mBlankScreen;
private boolean mScreenBlankingCallbackCalled;
@@ -265,12 +269,16 @@
private CoroutineDispatcher mMainDispatcher;
private boolean mIsBouncerToGoneTransitionRunning = false;
private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
- private final Consumer<Float> mScrimAlphaConsumer =
- (Float alpha) -> {
+ private final Consumer<ScrimAlpha> mScrimAlphaConsumer =
+ (ScrimAlpha alphas) -> {
+ mInFrontAlpha = alphas.getFrontAlpha();
mScrimInFront.setViewAlpha(mInFrontAlpha);
+
+ mNotificationsAlpha = alphas.getNotificationsAlpha();
mNotificationsScrim.setViewAlpha(mNotificationsAlpha);
- mBehindAlpha = alpha;
- mScrimBehind.setViewAlpha(alpha);
+
+ mBehindAlpha = alphas.getBehindAlpha();
+ mScrimBehind.setViewAlpha(mBehindAlpha);
};
Consumer<TransitionStep> mPrimaryBouncerToGoneTransition;
@@ -292,15 +300,17 @@
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
KeyguardTransitionInteractor keyguardTransitionInteractor,
- SysuiStatusBarStateController sysuiStatusBarStateController,
- @Main CoroutineDispatcher mainDispatcher) {
+ @Main CoroutineDispatcher mainDispatcher,
+ LargeScreenShadeInterpolator largeScreenShadeInterpolator,
+ FeatureFlags featureFlags) {
mScrimStateListener = lightBarController::setScrimState;
+ mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
+ mFeatureFlags = featureFlags;
mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
mKeyguardStateController = keyguardStateController;
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mStatusBarStateController = sysuiStatusBarStateController;
mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
mHandler = handler;
mMainExecutor = mainExecutor;
@@ -400,7 +410,7 @@
collectFlow(behindScrim, mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition(),
mPrimaryBouncerToGoneTransition, mMainDispatcher);
- collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimBehindAlpha(),
+ collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(),
mScrimAlphaConsumer, mMainDispatcher);
}
@@ -850,16 +860,21 @@
if (!mScreenOffAnimationController.shouldExpandNotifications()
&& !mAnimatingPanelExpansionOnUnlock
&& !occluding) {
- if (mClipsQsScrim) {
+ if (mTransparentScrimBackground) {
+ mBehindAlpha = 0;
+ mNotificationsAlpha = 0;
+ } else if (mClipsQsScrim) {
float behindFraction = getInterpolatedFraction();
behindFraction = (float) Math.pow(behindFraction, 0.8f);
- mBehindAlpha = mTransparentScrimBackground ? 0 : 1;
- mNotificationsAlpha =
- mTransparentScrimBackground ? 0 : behindFraction * mDefaultScrimAlpha;
+ mBehindAlpha = 1;
+ mNotificationsAlpha = behindFraction * mDefaultScrimAlpha;
} else {
- if (mTransparentScrimBackground) {
- mBehindAlpha = 0;
- mNotificationsAlpha = 0;
+ if (mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ mBehindAlpha = mLargeScreenShadeInterpolator.getBehindScrimAlpha(
+ mPanelExpansionFraction * mDefaultScrimAlpha);
+ mNotificationsAlpha =
+ mLargeScreenShadeInterpolator.getNotificationScrimAlpha(
+ mPanelExpansionFraction);
} else {
// Behind scrim will finish fading in at 30% expansion.
float behindFraction = MathUtils
@@ -1113,8 +1128,7 @@
mBehindAlpha = 1;
}
// Prevent notification scrim flicker when transitioning away from keyguard.
- if (mKeyguardStateController.isKeyguardGoingAway()
- && !mStatusBarStateController.leaveOpenOnKeyguardHide()) {
+ if (mKeyguardStateController.isKeyguardGoingAway()) {
mNotificationsAlpha = 0;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 11863627..04cc8ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -569,7 +569,10 @@
mGroup.addView(view, index, onCreateLayoutParams());
if (mIsInDemoMode) {
- mDemoStatusIcons.addModernMobileView(mContext, subId);
+ mDemoStatusIcons.addModernMobileView(
+ mContext,
+ mMobileIconsViewModel.getLogger(),
+ subId);
}
return view;
@@ -601,6 +604,7 @@
return ModernStatusBarMobileView
.constructAndBind(
mobileContext,
+ mMobileIconsViewModel.getLogger(),
slot,
mMobileIconsViewModel.viewModelForSub(subId, mLocation)
);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index f6c0da8..833cb93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -79,6 +79,18 @@
private @IconType int mType = TYPE_ICON;
private int mTag = 0;
+ /** Returns a human-readable string representing the given type. */
+ public static String getTypeString(@IconType int type) {
+ switch(type) {
+ case TYPE_ICON: return "ICON";
+ case TYPE_WIFI: return "WIFI_OLD";
+ case TYPE_MOBILE: return "MOBILE_OLD";
+ case TYPE_MOBILE_NEW: return "MOBILE_NEW";
+ case TYPE_WIFI_NEW: return "WIFI_NEW";
+ default: return "UNKNOWN";
+ }
+ }
+
private StatusBarIconHolder() {
}
@@ -230,4 +242,11 @@
public int getTag() {
return mTag;
}
+
+ @Override
+ public String toString() {
+ return "StatusBarIconHolder(type=" + getTypeString(mType)
+ + " tag=" + getTag()
+ + " visible=" + isVisible() + ")";
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
index 8800b05..565481a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
@@ -27,6 +27,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
/** A class holding the list of all the system icons that could be shown in the status bar. */
public class StatusBarIconList {
@@ -302,7 +303,7 @@
@Override
public String toString() {
- return String.format("(%s) %s", mName, subSlotsString());
+ return String.format("(%s) holder=%s %s", mName, mHolder, subSlotsString());
}
private String subSlotsString() {
@@ -310,7 +311,10 @@
return "";
}
- return "" + mSubSlots.size() + " subSlots";
+ return "| " + mSubSlots.size() + " subSlots: "
+ + mSubSlots.stream()
+ .map(StatusBarIconHolder::toString)
+ .collect(Collectors.joining("|"));
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
index 79c0984..d30d0e25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
@@ -22,6 +22,7 @@
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController.StatusBarViewsCenterProvider
import com.android.systemui.unfold.SysUIUnfoldScope
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import javax.inject.Inject
import kotlin.math.max
@@ -29,9 +30,13 @@
@SysUIUnfoldScope
class StatusBarMoveFromCenterAnimationController @Inject constructor(
private val progressProvider: ScopedUnfoldTransitionProgressProvider,
+ private val currentActivityTypeProvider: CurrentActivityTypeProvider,
windowManager: WindowManager
) {
+ // Whether we're on home activity. Updated only when the animation starts.
+ private var isOnHomeActivity: Boolean? = null
+
private val transitionListener = TransitionListener()
private val moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(
windowManager,
@@ -60,6 +65,10 @@
}
private inner class TransitionListener : TransitionProgressListener {
+ override fun onTransitionStarted() {
+ isOnHomeActivity = currentActivityTypeProvider.isHomeActivity
+ }
+
override fun onTransitionProgress(progress: Float) {
moveFromCenterAnimator.onTransitionProgress(progress)
}
@@ -68,11 +77,23 @@
// Reset translations when transition is stopped/cancelled
// (e.g. the transition could be cancelled mid-way when rotating the screen)
moveFromCenterAnimator.onTransitionProgress(1f)
+ isOnHomeActivity = null
}
}
- private class StatusBarIconsAlphaProvider : AlphaProvider {
+
+ /**
+ * In certain cases, an alpha is applied based on the progress.
+ *
+ * This mainly happens to hide the statusbar during the unfold animation while on apps, as the
+ * bounds of the app "collapse" to the center, but the statusbar doesn't.
+ * While on launcher, this alpha is not applied.
+ */
+ private inner class StatusBarIconsAlphaProvider : AlphaProvider {
override fun getAlpha(progress: Float): Float {
+ if (isOnHomeActivity == true) {
+ return 1.0f
+ }
return max(
0f,
(progress - ICONS_START_APPEARING_PROGRESS) / (1 - ICONS_START_APPEARING_PROGRESS)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileViewLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileViewLog.kt
new file mode 100644
index 0000000..e594a8a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileViewLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 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.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/** Logs for changes with the new mobile views. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class MobileViewLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 4464751..adfea80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -148,5 +148,19 @@
fun provideMobileInputLogBuffer(factory: LogBufferFactory): LogBuffer {
return factory.create("MobileInputLog", 100)
}
+
+ @Provides
+ @SysUISingleton
+ @MobileViewLog
+ fun provideMobileViewLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("MobileViewLog", 100)
+ }
+
+ @Provides
+ @SysUISingleton
+ @VerboseMobileViewLog
+ fun provideVerboseMobileViewLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("VerboseMobileViewLog", 100)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/VerboseMobileViewLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/VerboseMobileViewLog.kt
new file mode 100644
index 0000000..b987898
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/VerboseMobileViewLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 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.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/** Logs for **verbose** changes with the new mobile views. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class VerboseMobileViewLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
similarity index 88%
rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLogger.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index 3cbd2b7..4156fc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.mobile.shared
+package com.android.systemui.statusbar.pipeline.mobile.data
import android.net.Network
import android.net.NetworkCapabilities
@@ -51,8 +51,8 @@
)
}
- fun logOnLost(network: Network) {
- LoggerHelper.logOnLost(buffer, TAG, network)
+ fun logOnLost(network: Network, isDefaultNetworkCallback: Boolean) {
+ LoggerHelper.logOnLost(buffer, TAG, network, isDefaultNetworkCallback)
}
fun logOnServiceStateChanged(serviceState: ServiceState, subId: Int) {
@@ -133,24 +133,6 @@
)
}
- fun logUiAdapterSubIdsUpdated(subs: List<Int>) {
- buffer.log(
- TAG,
- LogLevel.INFO,
- { str1 = subs.toString() },
- { "Sub IDs in MobileUiAdapter updated internally: $str1" },
- )
- }
-
- fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) {
- buffer.log(
- TAG,
- LogLevel.INFO,
- { str1 = subs.toString() },
- { "Sub IDs in MobileUiAdapter being sent to icon controller: $str1" },
- )
- }
-
fun logCarrierConfigChanged(subId: Int) {
buffer.log(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
index 85729c1..19f0242 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
@@ -24,9 +24,11 @@
import android.telephony.TelephonyManager.DATA_SUSPENDED
import android.telephony.TelephonyManager.DATA_UNKNOWN
import android.telephony.TelephonyManager.DataState
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
/** Internal enum representation of the telephony data connection states */
-enum class DataConnectionState {
+enum class DataConnectionState : Diffable<DataConnectionState> {
Connected,
Connecting,
Disconnected,
@@ -34,7 +36,17 @@
Suspended,
HandoverInProgress,
Unknown,
- Invalid,
+ Invalid;
+
+ override fun logDiffs(prevVal: DataConnectionState, row: TableRowLogger) {
+ if (prevVal != this) {
+ row.logChange(COL_CONNECTION_STATE, name)
+ }
+ }
+
+ companion object {
+ private const val COL_CONNECTION_STATE = "connectionState"
+ }
}
fun @receiver:DataState Int.toDataConnectionType(): DataConnectionState =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
deleted file mode 100644
index ed7f60b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
+++ /dev/null
@@ -1,176 +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.statusbar.pipeline.mobile.data.model
-
-import android.annotation.IntRange
-import android.telephony.CellSignalStrength
-import android.telephony.TelephonyCallback.CarrierNetworkListener
-import android.telephony.TelephonyCallback.DataActivityListener
-import android.telephony.TelephonyCallback.DataConnectionStateListener
-import android.telephony.TelephonyCallback.DisplayInfoListener
-import android.telephony.TelephonyCallback.ServiceStateListener
-import android.telephony.TelephonyCallback.SignalStrengthsListener
-import android.telephony.TelephonyDisplayInfo
-import android.telephony.TelephonyManager
-import androidx.annotation.VisibleForTesting
-import com.android.systemui.log.table.Diffable
-import com.android.systemui.log.table.TableRowLogger
-import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-
-/**
- * Data class containing all of the relevant information for a particular line of service, known as
- * a Subscription in the telephony world. These models are the result of a single telephony listener
- * which has many callbacks which each modify some particular field on this object.
- *
- * The design goal here is to de-normalize fields from the system into our model fields below. So
- * any new field that needs to be tracked should be copied into this data class rather than
- * threading complex system objects through the pipeline.
- */
-data class MobileConnectionModel(
- /** Fields below are from [ServiceStateListener.onServiceStateChanged] */
- val isEmergencyOnly: Boolean = false,
- val isRoaming: Boolean = false,
- /**
- * See [android.telephony.ServiceState.getOperatorAlphaShort], this value is defined as the
- * current registered operator name in short alphanumeric format. In some cases this name might
- * be preferred over other methods of calculating the network name
- */
- val operatorAlphaShort: String? = null,
-
- /**
- * TODO (b/263167683): Clarify this field
- *
- * This check comes from [com.android.settingslib.Utils.isInService]. It is intended to be a
- * mapping from a ServiceState to a notion of connectivity. Notably, it will consider a
- * connection to be in-service if either the voice registration state is IN_SERVICE or the data
- * registration state is IN_SERVICE and NOT IWLAN.
- */
- val isInService: Boolean = false,
-
- /** Fields below from [SignalStrengthsListener.onSignalStrengthsChanged] */
- val isGsm: Boolean = false,
- @IntRange(from = 0, to = 4)
- val cdmaLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
- @IntRange(from = 0, to = 4)
- val primaryLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
-
- /** Fields below from [DataConnectionStateListener.onDataConnectionStateChanged] */
- val dataConnectionState: DataConnectionState = Disconnected,
-
- /**
- * Fields below from [DataActivityListener.onDataActivity]. See [TelephonyManager] for the
- * values
- */
- val dataActivityDirection: DataActivityModel =
- DataActivityModel(
- hasActivityIn = false,
- hasActivityOut = false,
- ),
-
- /** Fields below from [CarrierNetworkListener.onCarrierNetworkChange] */
- val carrierNetworkChangeActive: Boolean = false,
-
- /** Fields below from [DisplayInfoListener.onDisplayInfoChanged]. */
-
- /**
- * [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or
- * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
- */
- val resolvedNetworkType: ResolvedNetworkType = ResolvedNetworkType.UnknownNetworkType,
-) : Diffable<MobileConnectionModel> {
- override fun logDiffs(prevVal: MobileConnectionModel, row: TableRowLogger) {
- if (prevVal.dataConnectionState != dataConnectionState) {
- row.logChange(COL_CONNECTION_STATE, dataConnectionState.name)
- }
-
- if (prevVal.isEmergencyOnly != isEmergencyOnly) {
- row.logChange(COL_EMERGENCY, isEmergencyOnly)
- }
-
- if (prevVal.isRoaming != isRoaming) {
- row.logChange(COL_ROAMING, isRoaming)
- }
-
- if (prevVal.operatorAlphaShort != operatorAlphaShort) {
- row.logChange(COL_OPERATOR, operatorAlphaShort)
- }
-
- if (prevVal.isInService != isInService) {
- row.logChange(COL_IS_IN_SERVICE, isInService)
- }
-
- if (prevVal.isGsm != isGsm) {
- row.logChange(COL_IS_GSM, isGsm)
- }
-
- if (prevVal.cdmaLevel != cdmaLevel) {
- row.logChange(COL_CDMA_LEVEL, cdmaLevel)
- }
-
- if (prevVal.primaryLevel != primaryLevel) {
- row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
- }
-
- if (prevVal.dataActivityDirection.hasActivityIn != dataActivityDirection.hasActivityIn) {
- row.logChange(COL_ACTIVITY_DIRECTION_IN, dataActivityDirection.hasActivityIn)
- }
-
- if (prevVal.dataActivityDirection.hasActivityOut != dataActivityDirection.hasActivityOut) {
- row.logChange(COL_ACTIVITY_DIRECTION_OUT, dataActivityDirection.hasActivityOut)
- }
-
- if (prevVal.carrierNetworkChangeActive != carrierNetworkChangeActive) {
- row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChangeActive)
- }
-
- if (prevVal.resolvedNetworkType != resolvedNetworkType) {
- row.logChange(COL_RESOLVED_NETWORK_TYPE, resolvedNetworkType.toString())
- }
- }
-
- override fun logFull(row: TableRowLogger) {
- row.logChange(COL_CONNECTION_STATE, dataConnectionState.name)
- row.logChange(COL_EMERGENCY, isEmergencyOnly)
- row.logChange(COL_ROAMING, isRoaming)
- row.logChange(COL_OPERATOR, operatorAlphaShort)
- row.logChange(COL_IS_IN_SERVICE, isInService)
- row.logChange(COL_IS_GSM, isGsm)
- row.logChange(COL_CDMA_LEVEL, cdmaLevel)
- row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
- row.logChange(COL_ACTIVITY_DIRECTION_IN, dataActivityDirection.hasActivityIn)
- row.logChange(COL_ACTIVITY_DIRECTION_OUT, dataActivityDirection.hasActivityOut)
- row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChangeActive)
- row.logChange(COL_RESOLVED_NETWORK_TYPE, resolvedNetworkType.toString())
- }
-
- @VisibleForTesting
- companion object {
- const val COL_EMERGENCY = "EmergencyOnly"
- const val COL_ROAMING = "Roaming"
- const val COL_OPERATOR = "OperatorName"
- const val COL_IS_IN_SERVICE = "IsInService"
- const val COL_IS_GSM = "IsGsm"
- const val COL_CDMA_LEVEL = "CdmaLevel"
- const val COL_PRIMARY_LEVEL = "PrimaryLevel"
- const val COL_CONNECTION_STATE = "ConnectionState"
- const val COL_ACTIVITY_DIRECTION_IN = "DataActivity.In"
- const val COL_ACTIVITY_DIRECTION_OUT = "DataActivity.Out"
- const val COL_CARRIER_NETWORK_CHANGE = "CarrierNetworkChangeActive"
- const val COL_RESOLVED_NETWORK_TYPE = "NetworkType"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
index 5562e73..cf7a313 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
@@ -17,8 +17,12 @@
package com.android.systemui.statusbar.pipeline.mobile.data.model
import android.telephony.Annotation.NetworkType
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
/**
@@ -26,11 +30,19 @@
* on whether or not the display info contains an override type, we may have to call different
* methods on [MobileMappingsProxy] to generate an icon lookup key.
*/
-sealed interface ResolvedNetworkType {
+sealed interface ResolvedNetworkType : Diffable<ResolvedNetworkType> {
val lookupKey: String
+ override fun logDiffs(prevVal: ResolvedNetworkType, row: TableRowLogger) {
+ if (prevVal != this) {
+ row.logChange(COL_NETWORK_TYPE, this.toString())
+ }
+ }
+
object UnknownNetworkType : ResolvedNetworkType {
- override val lookupKey: String = "unknown"
+ override val lookupKey: String = MobileMappings.toIconKey(NETWORK_TYPE_UNKNOWN)
+
+ override fun toString(): String = "Unknown"
}
data class DefaultNetworkType(
@@ -47,5 +59,11 @@
override val lookupKey: String = "cwf"
val iconGroupOverride: SignalIcon.MobileIconGroup = TelephonyIcons.CARRIER_MERGED_WIFI
+
+ override fun toString(): String = "CarrierMerged"
+ }
+
+ companion object {
+ private const val COL_NETWORK_TYPE = "networkType"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
index bb3b9b2..efdce06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
@@ -30,8 +30,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 6187f64..90c32dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -17,11 +17,12 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.telephony.SubscriptionInfo
-import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.flow.StateFlow
/**
@@ -45,11 +46,57 @@
*/
val tableLogBuffer: TableLogBuffer
+ /** True if the [android.telephony.ServiceState] says this connection is emergency calls only */
+ val isEmergencyOnly: StateFlow<Boolean>
+
+ /** True if [android.telephony.ServiceState] says we are roaming */
+ val isRoaming: StateFlow<Boolean>
+
/**
- * A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
- * listener + model.
+ * See [android.telephony.ServiceState.getOperatorAlphaShort], this value is defined as the
+ * current registered operator name in short alphanumeric format. In some cases this name might
+ * be preferred over other methods of calculating the network name
*/
- val connectionInfo: StateFlow<MobileConnectionModel>
+ val operatorAlphaShort: StateFlow<String?>
+
+ /**
+ * TODO (b/263167683): Clarify this field
+ *
+ * This check comes from [com.android.settingslib.Utils.isInService]. It is intended to be a
+ * mapping from a ServiceState to a notion of connectivity. Notably, it will consider a
+ * connection to be in-service if either the voice registration state is IN_SERVICE or the data
+ * registration state is IN_SERVICE and NOT IWLAN.
+ */
+ val isInService: StateFlow<Boolean>
+
+ /** True if [android.telephony.SignalStrength] told us that this connection is using GSM */
+ val isGsm: StateFlow<Boolean>
+
+ /**
+ * There is still specific logic in the pipeline that calls out CDMA level explicitly. This
+ * field is not completely orthogonal to [primaryLevel], because CDMA could be primary.
+ */
+ // @IntRange(from = 0, to = 4)
+ val cdmaLevel: StateFlow<Int>
+
+ /** [android.telephony.SignalStrength]'s concept of the overall signal level */
+ // @IntRange(from = 0, to = 4)
+ val primaryLevel: StateFlow<Int>
+
+ /** The current data connection state. See [DataConnectionState] */
+ val dataConnectionState: StateFlow<DataConnectionState>
+
+ /** The current data activity direction. See [DataActivityModel] */
+ val dataActivityDirection: StateFlow<DataActivityModel>
+
+ /** True if there is currently a carrier network change in process */
+ val carrierNetworkChangeActive: StateFlow<Boolean>
+
+ /**
+ * [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or
+ * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
+ */
+ val resolvedNetworkType: StateFlow<ResolvedNetworkType>
/** The total number of levels. Used with [SignalDrawable]. */
val numberOfLevels: StateFlow<Int>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
new file mode 100644
index 0000000..809772e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2023 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.pipeline.mobile.data.repository.demo
+
+import android.telephony.CellSignalStrength
+import android.telephony.TelephonyManager
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CARRIER_NETWORK_CHANGE
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CDMA_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_IS_GSM
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_IS_IN_SERVICE
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_OPERATOR
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_PRIMARY_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_ROAMING
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Demo version of [MobileConnectionRepository]. Note that this class shares all of its flows using
+ * [SharingStarted.WhileSubscribed()] to give the same semantics as using a regular
+ * [MutableStateFlow] while still logging all of the inputs in the same manor as the production
+ * repos.
+ */
+class DemoMobileConnectionRepository(
+ override val subId: Int,
+ override val tableLogBuffer: TableLogBuffer,
+ val scope: CoroutineScope,
+) : MobileConnectionRepository {
+ private val _isEmergencyOnly = MutableStateFlow(false)
+ override val isEmergencyOnly =
+ _isEmergencyOnly
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_EMERGENCY,
+ _isEmergencyOnly.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _isEmergencyOnly.value)
+
+ private val _isRoaming = MutableStateFlow(false)
+ override val isRoaming =
+ _isRoaming
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_ROAMING,
+ _isRoaming.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _isRoaming.value)
+
+ private val _operatorAlphaShort: MutableStateFlow<String?> = MutableStateFlow(null)
+ override val operatorAlphaShort =
+ _operatorAlphaShort
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_OPERATOR,
+ _operatorAlphaShort.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _operatorAlphaShort.value)
+
+ private val _isInService = MutableStateFlow(false)
+ override val isInService =
+ _isInService
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_IS_IN_SERVICE,
+ _isInService.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _isInService.value)
+
+ private val _isGsm = MutableStateFlow(false)
+ override val isGsm =
+ _isGsm
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_IS_GSM,
+ _isGsm.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _isGsm.value)
+
+ private val _cdmaLevel = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ override val cdmaLevel =
+ _cdmaLevel
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_CDMA_LEVEL,
+ _cdmaLevel.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _cdmaLevel.value)
+
+ private val _primaryLevel = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ override val primaryLevel =
+ _primaryLevel
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_PRIMARY_LEVEL,
+ _primaryLevel.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _primaryLevel.value)
+
+ private val _dataConnectionState = MutableStateFlow(DataConnectionState.Disconnected)
+ override val dataConnectionState =
+ _dataConnectionState
+ .logDiffsForTable(tableLogBuffer, columnPrefix = "", _dataConnectionState.value)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _dataConnectionState.value)
+
+ private val _dataActivityDirection =
+ MutableStateFlow(
+ DataActivityModel(
+ hasActivityIn = false,
+ hasActivityOut = false,
+ )
+ )
+ override val dataActivityDirection =
+ _dataActivityDirection
+ .logDiffsForTable(tableLogBuffer, columnPrefix = "", _dataActivityDirection.value)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _dataActivityDirection.value)
+
+ private val _carrierNetworkChangeActive = MutableStateFlow(false)
+ override val carrierNetworkChangeActive =
+ _carrierNetworkChangeActive
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_CARRIER_NETWORK_CHANGE,
+ _carrierNetworkChangeActive.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _carrierNetworkChangeActive.value)
+
+ private val _resolvedNetworkType: MutableStateFlow<ResolvedNetworkType> =
+ MutableStateFlow(ResolvedNetworkType.UnknownNetworkType)
+ override val resolvedNetworkType =
+ _resolvedNetworkType
+ .logDiffsForTable(tableLogBuffer, columnPrefix = "", _resolvedNetworkType.value)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _resolvedNetworkType.value)
+
+ override val numberOfLevels = MutableStateFlow(MobileConnectionRepository.DEFAULT_NUM_LEVELS)
+
+ override val dataEnabled = MutableStateFlow(true)
+
+ override val cdmaRoaming = MutableStateFlow(false)
+
+ override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo network"))
+
+ /**
+ * Process a new demo mobile event. Note that [resolvedNetworkType] must be passed in separately
+ * from the event, due to the requirement to reverse the mobile mappings lookup in the top-level
+ * repository.
+ */
+ fun processDemoMobileEvent(
+ event: FakeNetworkEventModel.Mobile,
+ resolvedNetworkType: ResolvedNetworkType,
+ ) {
+ // This is always true here, because we split out disabled states at the data-source level
+ dataEnabled.value = true
+ networkName.value = NetworkNameModel.IntentDerived(event.name)
+
+ cdmaRoaming.value = event.roaming
+ _isRoaming.value = event.roaming
+ // TODO(b/261029387): not yet supported
+ _isEmergencyOnly.value = false
+ _operatorAlphaShort.value = event.name
+ _isInService.value = (event.level ?: 0) > 0
+ // TODO(b/261029387): not yet supported
+ _isGsm.value = false
+ _cdmaLevel.value = event.level ?: 0
+ _primaryLevel.value = event.level ?: 0
+ // TODO(b/261029387): not yet supported
+ _dataConnectionState.value = DataConnectionState.Connected
+ _dataActivityDirection.value =
+ (event.activity ?: TelephonyManager.DATA_ACTIVITY_NONE).toMobileDataActivityModel()
+ _carrierNetworkChangeActive.value = event.carrierNetworkChange
+ _resolvedNetworkType.value = resolvedNetworkType
+ }
+
+ fun processCarrierMergedEvent(event: FakeWifiEventModel.CarrierMerged) {
+ // This is always true here, because we split out disabled states at the data-source level
+ dataEnabled.value = true
+ networkName.value = NetworkNameModel.IntentDerived(CARRIER_MERGED_NAME)
+ numberOfLevels.value = event.numberOfLevels
+ cdmaRoaming.value = false
+ _primaryLevel.value = event.level
+ _cdmaLevel.value = event.level
+ _dataActivityDirection.value = event.activity.toMobileDataActivityModel()
+
+ // These fields are always the same for carrier-merged networks
+ _resolvedNetworkType.value = ResolvedNetworkType.CarrierMergedNetworkType
+ _dataConnectionState.value = DataConnectionState.Connected
+ _isRoaming.value = false
+ _isEmergencyOnly.value = false
+ _operatorAlphaShort.value = null
+ _isInService.value = true
+ _isGsm.value = false
+ _carrierNetworkChangeActive.value = false
+ }
+
+ companion object {
+ private const val CARRIER_MERGED_NAME = "Carrier Merged Network"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index e924832..3cafb73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -18,30 +18,22 @@
import android.content.Context
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
import android.util.Log
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
-import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
-import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.CarrierMergedConnectionRepository.Companion.createCarrierMergedConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.MOBILE_CONNECTION_BUFFER_SIZE
-import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
import javax.inject.Inject
@@ -183,7 +175,7 @@
private fun createDemoMobileConnectionRepo(subId: Int): CacheContainer {
val tableLogBuffer =
logFactory.getOrCreate(
- "DemoMobileConnectionLog [$subId]",
+ "DemoMobileConnectionLog[$subId]",
MOBILE_CONNECTION_BUFFER_SIZE,
)
@@ -191,6 +183,7 @@
DemoMobileConnectionRepository(
subId,
tableLogBuffer,
+ scope,
)
return CacheContainer(repo, lastMobileState = null)
}
@@ -237,23 +230,18 @@
}
}
- private fun processEnabledMobileState(state: Mobile) {
+ private fun processEnabledMobileState(event: Mobile) {
// get or create the connection repo, and set its values
- val subId = state.subId ?: DEFAULT_SUB_ID
+ val subId = event.subId ?: DEFAULT_SUB_ID
maybeCreateSubscription(subId)
val connection = getRepoForSubId(subId)
- connectionRepoCache[subId]?.lastMobileState = state
+ connectionRepoCache[subId]?.lastMobileState = event
// TODO(b/261029387): until we have a command, use the most recent subId
defaultDataSubId.value = subId
- // This is always true here, because we split out disabled states at the data-source level
- connection.dataEnabled.value = true
- connection.networkName.value = NetworkNameModel.IntentDerived(state.name)
-
- connection.cdmaRoaming.value = state.roaming
- connection.connectionInfo.value = state.toMobileConnectionModel()
+ connection.processDemoMobileEvent(event, event.dataType.toResolvedNetworkType())
}
private fun processCarrierMergedWifiState(event: FakeWifiEventModel.CarrierMerged) {
@@ -272,13 +260,7 @@
defaultDataSubId.value = subId
val connection = getRepoForSubId(subId)
- // This is always true here, because we split out disabled states at the data-source level
- connection.dataEnabled.value = true
- connection.networkName.value = NetworkNameModel.IntentDerived(CARRIER_MERGED_NAME)
- connection.numberOfLevels.value = event.numberOfLevels
- connection.cdmaRoaming.value = false
- connection.connectionInfo.value = event.toMobileConnectionModel()
- Log.e("CCS", "output connection info = ${connection.connectionInfo.value}")
+ connection.processCarrierMergedEvent(event)
}
private fun maybeRemoveSubscription(subId: Int?) {
@@ -332,29 +314,6 @@
private fun subIdsString(): String =
_subscriptions.value.joinToString(",") { it.subscriptionId.toString() }
- private fun Mobile.toMobileConnectionModel(): MobileConnectionModel {
- return MobileConnectionModel(
- isEmergencyOnly = false, // TODO(b/261029387): not yet supported
- isRoaming = roaming,
- isInService = (level ?: 0) > 0,
- isGsm = false, // TODO(b/261029387): not yet supported
- cdmaLevel = level ?: 0,
- primaryLevel = level ?: 0,
- dataConnectionState =
- DataConnectionState.Connected, // TODO(b/261029387): not yet supported
- dataActivityDirection = (activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel(),
- carrierNetworkChangeActive = carrierNetworkChange,
- resolvedNetworkType = dataType.toResolvedNetworkType()
- )
- }
-
- private fun FakeWifiEventModel.CarrierMerged.toMobileConnectionModel(): MobileConnectionModel {
- return createCarrierMergedConnectionModel(
- this.level,
- activity.toMobileDataActivityModel(),
- )
- }
-
private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType {
val key = mobileMappingsReverseLookup.value[this] ?: "dis"
return DefaultNetworkType(key)
@@ -364,8 +323,6 @@
private const val TAG = "DemoMobileConnectionsRepo"
private const val DEFAULT_SUB_ID = 1
-
- private const val CARRIER_MERGED_NAME = "Carrier Merged Network"
}
}
@@ -374,18 +331,3 @@
/** The last received [Mobile] event. Used when switching from carrier merged back to mobile. */
var lastMobileState: Mobile?,
)
-
-class DemoMobileConnectionRepository(
- override val subId: Int,
- override val tableLogBuffer: TableLogBuffer,
-) : MobileConnectionRepository {
- override val connectionInfo = MutableStateFlow(MobileConnectionModel())
-
- override val numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
-
- override val dataEnabled = MutableStateFlow(true)
-
- override val cdmaRoaming = MutableStateFlow(false)
-
- override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo network"))
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index 8f6a87b..94d6d0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -16,18 +16,17 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
import android.telephony.TelephonyManager
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import javax.inject.Inject
@@ -94,16 +93,6 @@
}
}
- override val connectionInfo: StateFlow<MobileConnectionModel> =
- combine(network, wifiRepository.wifiActivity) { network, activity ->
- if (network == null) {
- MobileConnectionModel()
- } else {
- createCarrierMergedConnectionModel(network.level, activity)
- }
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectionModel())
-
override val cdmaRoaming: StateFlow<Boolean> = MutableStateFlow(ROAMING).asStateFlow()
override val networkName: StateFlow<NetworkNameModel> =
@@ -129,34 +118,54 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_NUM_LEVELS)
+ override val primaryLevel =
+ network
+ .map { it?.level ?: SIGNAL_STRENGTH_NONE_OR_UNKNOWN }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+ override val cdmaLevel =
+ network
+ .map { it?.level ?: SIGNAL_STRENGTH_NONE_OR_UNKNOWN }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+ override val dataActivityDirection = wifiRepository.wifiActivity
+
+ override val resolvedNetworkType =
+ network
+ .map {
+ if (it != null) {
+ ResolvedNetworkType.CarrierMergedNetworkType
+ } else {
+ ResolvedNetworkType.UnknownNetworkType
+ }
+ }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ ResolvedNetworkType.UnknownNetworkType
+ )
+
+ override val dataConnectionState =
+ network
+ .map {
+ if (it != null) {
+ DataConnectionState.Connected
+ } else {
+ DataConnectionState.Disconnected
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), DataConnectionState.Disconnected)
+
+ override val isRoaming = MutableStateFlow(false).asStateFlow()
+ override val isEmergencyOnly = MutableStateFlow(false).asStateFlow()
+ override val operatorAlphaShort = MutableStateFlow(null).asStateFlow()
+ override val isInService = MutableStateFlow(true).asStateFlow()
+ override val isGsm = MutableStateFlow(false).asStateFlow()
+ override val carrierNetworkChangeActive = MutableStateFlow(false).asStateFlow()
+
override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
companion object {
- /**
- * Creates an instance of [MobileConnectionModel] that represents a carrier merged network
- * with the given [level] and [activity].
- */
- fun createCarrierMergedConnectionModel(
- level: Int,
- activity: DataActivityModel,
- ): MobileConnectionModel {
- return MobileConnectionModel(
- primaryLevel = level,
- cdmaLevel = level,
- dataActivityDirection = activity,
- // Here and below: These values are always the same for every carrier-merged
- // connection.
- resolvedNetworkType = ResolvedNetworkType.CarrierMergedNetworkType,
- dataConnectionState = DataConnectionState.Connected,
- isRoaming = ROAMING,
- isEmergencyOnly = false,
- operatorAlphaShort = null,
- isInService = true,
- isGsm = false,
- carrierNetworkChangeActive = false,
- )
- }
-
// Carrier merged is never roaming
private const val ROAMING = false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index a39ea0a..b3737ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -114,15 +114,147 @@
.flatMapLatest { it.cdmaRoaming }
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.cdmaRoaming.value)
- override val connectionInfo =
+ override val isEmergencyOnly =
activeRepo
- .flatMapLatest { it.connectionInfo }
+ .flatMapLatest { it.isEmergencyOnly }
.logDiffsForTable(
tableLogBuffer,
columnPrefix = "",
- initialValue = activeRepo.value.connectionInfo.value,
+ columnName = COL_EMERGENCY,
+ activeRepo.value.isEmergencyOnly.value
)
- .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.connectionInfo.value)
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.isEmergencyOnly.value
+ )
+
+ override val isRoaming =
+ activeRepo
+ .flatMapLatest { it.isRoaming }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_ROAMING,
+ activeRepo.value.isRoaming.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isRoaming.value)
+
+ override val operatorAlphaShort =
+ activeRepo
+ .flatMapLatest { it.operatorAlphaShort }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_OPERATOR,
+ activeRepo.value.operatorAlphaShort.value
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.operatorAlphaShort.value
+ )
+
+ override val isInService =
+ activeRepo
+ .flatMapLatest { it.isInService }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_IS_IN_SERVICE,
+ activeRepo.value.isInService.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isInService.value)
+
+ override val isGsm =
+ activeRepo
+ .flatMapLatest { it.isGsm }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_IS_GSM,
+ activeRepo.value.isGsm.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isGsm.value)
+
+ override val cdmaLevel =
+ activeRepo
+ .flatMapLatest { it.cdmaLevel }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_CDMA_LEVEL,
+ activeRepo.value.cdmaLevel.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.cdmaLevel.value)
+
+ override val primaryLevel =
+ activeRepo
+ .flatMapLatest { it.primaryLevel }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_PRIMARY_LEVEL,
+ activeRepo.value.primaryLevel.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.primaryLevel.value)
+
+ override val dataConnectionState =
+ activeRepo
+ .flatMapLatest { it.dataConnectionState }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ activeRepo.value.dataConnectionState.value
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.dataConnectionState.value
+ )
+
+ override val dataActivityDirection =
+ activeRepo
+ .flatMapLatest { it.dataActivityDirection }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ activeRepo.value.dataActivityDirection.value
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.dataActivityDirection.value
+ )
+
+ override val carrierNetworkChangeActive =
+ activeRepo
+ .flatMapLatest { it.carrierNetworkChangeActive }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_CARRIER_NETWORK_CHANGE,
+ activeRepo.value.carrierNetworkChangeActive.value
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.carrierNetworkChangeActive.value
+ )
+
+ override val resolvedNetworkType =
+ activeRepo
+ .flatMapLatest { it.resolvedNetworkType }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ activeRepo.value.resolvedNetworkType.value
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.resolvedNetworkType.value
+ )
override val dataEnabled =
activeRepo
@@ -187,4 +319,15 @@
fun tableBufferLogName(subId: Int): String = "MobileConnectionLog[$subId]"
}
}
+
+ companion object {
+ const val COL_EMERGENCY = "emergencyOnly"
+ const val COL_ROAMING = "roaming"
+ const val COL_OPERATOR = "operatorName"
+ const val COL_IS_IN_SERVICE = "isInService"
+ const val COL_IS_GSM = "isGsm"
+ const val COL_CDMA_LEVEL = "cdmaLevel"
+ const val COL_PRIMARY_LEVEL = "primaryLevel"
+ const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChangeActive"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 96b96f1..f1fc386 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.content.IntentFilter
import android.telephony.CellSignalStrength
+import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
import android.telephony.SignalStrength
@@ -36,7 +37,8 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
@@ -47,8 +49,8 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -62,10 +64,10 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.mapNotNull
-import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
@@ -165,83 +167,100 @@
}
.shareIn(scope, SharingStarted.WhileSubscribed())
- private fun updateConnectionState(
- prevState: MobileConnectionModel,
- callbackEvent: CallbackEvent,
- ): MobileConnectionModel =
- when (callbackEvent) {
- is CallbackEvent.OnServiceStateChanged -> {
- val serviceState = callbackEvent.serviceState
- prevState.copy(
- isEmergencyOnly = serviceState.isEmergencyOnly,
- isRoaming = serviceState.roaming,
- operatorAlphaShort = serviceState.operatorAlphaShort,
- isInService = Utils.isInService(serviceState),
- )
- }
- is CallbackEvent.OnSignalStrengthChanged -> {
- val signalStrength = callbackEvent.signalStrength
- val cdmaLevel =
- signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java).let {
- strengths ->
- if (!strengths.isEmpty()) {
- strengths[0].level
- } else {
- CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
- }
- }
-
- val primaryLevel = signalStrength.level
-
- prevState.copy(
- cdmaLevel = cdmaLevel,
- primaryLevel = primaryLevel,
- isGsm = signalStrength.isGsm,
- )
- }
- is CallbackEvent.OnDataConnectionStateChanged -> {
- prevState.copy(dataConnectionState = callbackEvent.dataState.toDataConnectionType())
- }
- is CallbackEvent.OnDataActivity -> {
- prevState.copy(
- dataActivityDirection = callbackEvent.direction.toMobileDataActivityModel()
- )
- }
- is CallbackEvent.OnCarrierNetworkChange -> {
- prevState.copy(carrierNetworkChangeActive = callbackEvent.active)
- }
- is CallbackEvent.OnDisplayInfoChanged -> {
- val telephonyDisplayInfo = callbackEvent.telephonyDisplayInfo
- val networkType =
- if (telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
- UnknownNetworkType
- } else if (
- telephonyDisplayInfo.overrideNetworkType == OVERRIDE_NETWORK_TYPE_NONE
- ) {
- DefaultNetworkType(
- mobileMappingsProxy.toIconKey(telephonyDisplayInfo.networkType)
- )
- } else {
- OverrideNetworkType(
- mobileMappingsProxy.toIconKeyOverride(
- telephonyDisplayInfo.overrideNetworkType
- )
- )
- }
- prevState.copy(resolvedNetworkType = networkType)
- }
- is CallbackEvent.OnDataEnabledChanged -> {
- // Not part of this object, handled in a separate flow
- prevState
- }
- }
-
- override val connectionInfo = run {
- val initial = MobileConnectionModel()
+ override val isEmergencyOnly =
callbackEvents
- .scan(initial, ::updateConnectionState)
- .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
- }
+ .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+ .map { it.serviceState.isEmergencyOnly }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val isRoaming =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+ .map { it.serviceState.roaming }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val operatorAlphaShort =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+ .map { it.serviceState.operatorAlphaShort }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ override val isInService =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+ .map { Utils.isInService(it.serviceState) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val isGsm =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+ .map { it.signalStrength.isGsm }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val cdmaLevel =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+ .map {
+ it.signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java).let {
+ strengths ->
+ if (strengths.isNotEmpty()) {
+ strengths[0].level
+ } else {
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+ }
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+ override val primaryLevel =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+ .map { it.signalStrength.level }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+ override val dataConnectionState =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnDataConnectionStateChanged>()
+ .map { it.dataState.toDataConnectionType() }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), Disconnected)
+
+ override val dataActivityDirection =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnDataActivity>()
+ .map { it.direction.toMobileDataActivityModel() }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ )
+
+ override val carrierNetworkChangeActive =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnCarrierNetworkChange>()
+ .map { it.active }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val resolvedNetworkType =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnDisplayInfoChanged>()
+ .map {
+ if (it.telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
+ UnknownNetworkType
+ } else if (
+ it.telephonyDisplayInfo.overrideNetworkType == OVERRIDE_NETWORK_TYPE_NONE
+ ) {
+ DefaultNetworkType(
+ mobileMappingsProxy.toIconKey(it.telephonyDisplayInfo.networkType)
+ )
+ } else {
+ OverrideNetworkType(
+ mobileMappingsProxy.toIconKeyOverride(
+ it.telephonyDisplayInfo.overrideNetworkType
+ )
+ )
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), UnknownNetworkType)
override val numberOfLevels =
systemUiCarrierConfig.shouldInflateSignalStrength
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 53a208c..b7da3f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -45,11 +45,11 @@
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
@@ -266,6 +266,7 @@
val callback =
object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
override fun onLost(network: Network) {
+ logger.logOnLost(network, isDefaultNetworkCallback = true)
// Send a disconnected model when lost. Maybe should create a sealed
// type or null here?
trySend(MobileConnectivityModel())
@@ -275,6 +276,11 @@
network: Network,
caps: NetworkCapabilities
) {
+ logger.logOnCapabilitiesChanged(
+ network,
+ caps,
+ isDefaultNetworkCallback = true,
+ )
trySend(
MobileConnectivityModel(
isConnected = caps.hasTransport(TRANSPORT_CELLULAR),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 4caf2b0..7df6764 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -34,6 +34,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
@@ -133,11 +134,9 @@
override val isForceHidden: Flow<Boolean>,
connectionRepository: MobileConnectionRepository,
) : MobileIconInteractor {
- private val connectionInfo = connectionRepository.connectionInfo
-
override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer
- override val activity = connectionInfo.mapLatest { it.dataActivityDirection }
+ override val activity = connectionRepository.dataActivityDirection
override val isConnected: Flow<Boolean> = defaultMobileConnectivity.mapLatest { it.isConnected }
@@ -155,11 +154,11 @@
override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled
override val networkName =
- combine(connectionInfo, connectionRepository.networkName) { connection, networkName ->
- if (
- networkName is NetworkNameModel.Default && connection.operatorAlphaShort != null
- ) {
- NetworkNameModel.IntentDerived(connection.operatorAlphaShort)
+ combine(connectionRepository.operatorAlphaShort, connectionRepository.networkName) {
+ operatorAlphaShort,
+ networkName ->
+ if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) {
+ NetworkNameModel.IntentDerived(operatorAlphaShort)
} else {
networkName
}
@@ -173,19 +172,19 @@
/** Observable for the current RAT indicator icon ([MobileIconGroup]) */
override val networkTypeIconGroup: StateFlow<MobileIconGroup> =
combine(
- connectionInfo,
+ connectionRepository.resolvedNetworkType,
defaultMobileIconMapping,
defaultMobileIconGroup,
isDefault,
- ) { info, mapping, defaultGroup, isDefault ->
+ ) { resolvedNetworkType, mapping, defaultGroup, isDefault ->
if (!isDefault) {
return@combine NOT_DEFAULT_DATA
}
- when (info.resolvedNetworkType) {
+ when (resolvedNetworkType) {
is ResolvedNetworkType.CarrierMergedNetworkType ->
- info.resolvedNetworkType.iconGroupOverride
- else -> mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup
+ resolvedNetworkType.iconGroupOverride
+ else -> mapping[resolvedNetworkType.lookupKey] ?: defaultGroup
}
}
.distinctUntilChanged()
@@ -200,17 +199,19 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
- override val isEmergencyOnly: StateFlow<Boolean> =
- connectionInfo
- .mapLatest { it.isEmergencyOnly }
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val isEmergencyOnly = connectionRepository.isEmergencyOnly
override val isRoaming: StateFlow<Boolean> =
- combine(connectionInfo, connectionRepository.cdmaRoaming) { connection, cdmaRoaming ->
- if (connection.carrierNetworkChangeActive) {
+ combine(
+ connectionRepository.carrierNetworkChangeActive,
+ connectionRepository.isGsm,
+ connectionRepository.isRoaming,
+ connectionRepository.cdmaRoaming,
+ ) { carrierNetworkChangeActive, isGsm, isRoaming, cdmaRoaming ->
+ if (carrierNetworkChangeActive) {
false
- } else if (connection.isGsm) {
- connection.isRoaming
+ } else if (isGsm) {
+ isRoaming
} else {
cdmaRoaming
}
@@ -218,12 +219,17 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val level: StateFlow<Int> =
- combine(connectionInfo, alwaysUseCdmaLevel) { connection, alwaysUseCdmaLevel ->
+ combine(
+ connectionRepository.isGsm,
+ connectionRepository.primaryLevel,
+ connectionRepository.cdmaLevel,
+ alwaysUseCdmaLevel,
+ ) { isGsm, primaryLevel, cdmaLevel, alwaysUseCdmaLevel ->
when {
// GSM connections should never use the CDMA level
- connection.isGsm -> connection.primaryLevel
- alwaysUseCdmaLevel -> connection.cdmaLevel
- else -> connection.primaryLevel
+ isGsm -> primaryLevel
+ alwaysUseCdmaLevel -> cdmaLevel
+ else -> primaryLevel
}
}
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
@@ -236,12 +242,9 @@
)
override val isDataConnected: StateFlow<Boolean> =
- connectionInfo
- .mapLatest { connection -> connection.dataConnectionState == Connected }
+ connectionRepository.dataConnectionState
+ .map { it == Connected }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- override val isInService =
- connectionRepository.connectionInfo
- .mapLatest { it.isInService }
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val isInService = connectionRepository.isInService
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index da63ab1..075e6ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -22,7 +22,6 @@
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import java.io.PrintWriter
import javax.inject.Inject
@@ -55,17 +54,14 @@
interactor: MobileIconsInteractor,
private val iconController: StatusBarIconController,
private val iconsViewModelFactory: MobileIconsViewModel.Factory,
- private val logger: MobileInputLogger,
+ private val logger: MobileViewLogger,
@Application private val scope: CoroutineScope,
private val statusBarPipelineFlags: StatusBarPipelineFlags,
) : CoreStartable {
private val mobileSubIds: Flow<List<Int>> =
- interactor.filteredSubscriptions
- .mapLatest { subscriptions ->
- subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
- }
- .distinctUntilChanged()
- .onEach { logger.logUiAdapterSubIdsUpdated(it) }
+ interactor.filteredSubscriptions.mapLatest { subscriptions ->
+ subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
+ }
/**
* We expose the list of tracked subscriptions as a flow of a list of ints, where each int is
@@ -75,7 +71,10 @@
* NOTE: this should go away as the view presenter learns more about this data pipeline
*/
private val mobileSubIdsState: StateFlow<List<Int>> =
- mobileSubIds.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+ mobileSubIds
+ .distinctUntilChanged()
+ .onEach { logger.logUiAdapterSubIdsUpdated(it) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
/** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */
val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
new file mode 100644
index 0000000..90dff23
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023 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.pipeline.mobile.ui
+
+import android.view.View
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.statusbar.pipeline.dagger.MobileViewLog
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/** Logs for changes with the new mobile views. */
+@SysUISingleton
+class MobileViewLogger
+@Inject
+constructor(
+ @MobileViewLog private val buffer: LogBuffer,
+ dumpManager: DumpManager,
+) : Dumpable {
+ init {
+ dumpManager.registerNormalDumpable(this)
+ }
+
+ private val collectionStatuses = mutableMapOf<String, Boolean>()
+
+ fun logUiAdapterSubIdsUpdated(subs: List<Int>) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ { str1 = subs.toString() },
+ { "Sub IDs in MobileUiAdapter updated internally: $str1" },
+ )
+ }
+
+ fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ { str1 = subs.toString() },
+ { "Sub IDs in MobileUiAdapter being sent to icon controller: $str1" },
+ )
+ }
+
+ fun logNewViewBinding(view: View, viewModel: LocationBasedMobileViewModel) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ str1 = view.getIdForLogging()
+ str2 = viewModel.getIdForLogging()
+ str3 = viewModel.locationName
+ },
+ { "New view binding. viewId=$str1, viewModelId=$str2, viewModelLocation=$str3" },
+ )
+ }
+
+ fun logCollectionStarted(view: View, viewModel: LocationBasedMobileViewModel) {
+ collectionStatuses[view.getIdForLogging()] = true
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ str1 = view.getIdForLogging()
+ str2 = viewModel.getIdForLogging()
+ str3 = viewModel.locationName
+ },
+ { "Collection started. viewId=$str1, viewModelId=$str2, viewModelLocation=$str3" },
+ )
+ }
+
+ fun logCollectionStopped(view: View, viewModel: LocationBasedMobileViewModel) {
+ collectionStatuses[view.getIdForLogging()] = false
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ str1 = view.getIdForLogging()
+ str2 = viewModel.getIdForLogging()
+ str3 = viewModel.locationName
+ },
+ { "Collection stopped. viewId=$str1, viewModelId=$str2, viewModelLocation=$str3" },
+ )
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("Collection statuses per view:---")
+ collectionStatuses.forEach { viewId, isCollecting ->
+ pw.println("viewId=$viewId, isCollecting=$isCollecting")
+ }
+ }
+
+ companion object {
+ fun Any.getIdForLogging(): String {
+ // The identityHashCode is guaranteed to be constant for the lifetime of the object.
+ return Integer.toHexString(System.identityHashCode(this))
+ }
+ }
+}
+
+private const val TAG = "MobileViewLogger"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
new file mode 100644
index 0000000..f67bc8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 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.pipeline.mobile.ui
+
+import android.view.View
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.statusbar.pipeline.dagger.VerboseMobileViewLog
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging
+import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
+import javax.inject.Inject
+
+/**
+ * Logs for **verbose** changes with the new mobile views.
+ *
+ * This is a hopefully temporary log until we resolve some open bugs (b/267236367, b/269565345,
+ * b/270300839).
+ */
+@SysUISingleton
+class VerboseMobileViewLogger
+@Inject
+constructor(
+ @VerboseMobileViewLog private val buffer: LogBuffer,
+) {
+ fun logBinderReceivedSignalIcon(parentView: View, subId: Int, icon: SignalIconModel) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = parentView.getIdForLogging()
+ int1 = subId
+ int2 = icon.level
+ bool1 = icon.showExclamationMark
+ },
+ {
+ "Binder[subId=$int1, viewId=$str1] received new signal icon: " +
+ "level=$int2 showExclamation=$bool1"
+ },
+ )
+ }
+
+ fun logBinderReceivedNetworkTypeIcon(parentView: View, subId: Int, icon: Icon.Resource?) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = parentView.getIdForLogging()
+ int1 = subId
+ bool1 = icon != null
+ int2 = icon?.res ?: -1
+ },
+ {
+ "Binder[subId=$int1, viewId=$str1] received new network type icon: " +
+ if (bool1) "resId=$int2" else "null"
+ },
+ )
+ }
+}
+
+private const val TAG = "VerboseMobileViewLogger"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index db585e6..5b7d45b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -36,8 +36,10 @@
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
+import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
@@ -48,6 +50,7 @@
fun bind(
view: ViewGroup,
viewModel: LocationBasedMobileViewModel,
+ logger: MobileViewLogger,
): ModernStatusBarViewBinding {
val mobileGroupView = view.requireViewById<ViewGroup>(R.id.mobile_group)
val activityContainer = view.requireViewById<View>(R.id.inout_container)
@@ -70,8 +73,13 @@
val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+ var isCollecting: Boolean = false
+
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
+ logger.logCollectionStarted(view, viewModel)
+ isCollecting = true
+
launch {
visibilityState.collect { state ->
when (state) {
@@ -96,6 +104,11 @@
// Set the icon for the triangle
launch {
viewModel.icon.distinctUntilChanged().collect { icon ->
+ viewModel.verboseLogger?.logBinderReceivedSignalIcon(
+ view,
+ viewModel.subscriptionId,
+ icon,
+ )
mobileDrawable.level =
SignalDrawable.getState(
icon.level,
@@ -114,6 +127,11 @@
// Set the network type icon
launch {
viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId ->
+ viewModel.verboseLogger?.logBinderReceivedNetworkTypeIcon(
+ view,
+ viewModel.subscriptionId,
+ dataTypeId,
+ )
dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) }
networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE
}
@@ -150,6 +168,13 @@
}
launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } }
+
+ try {
+ awaitCancellation()
+ } finally {
+ isCollecting = false
+ logger.logCollectionStopped(view, viewModel)
+ }
}
}
@@ -175,6 +200,10 @@
}
decorTint.value = newTint
}
+
+ override fun isCollecting(): Boolean {
+ return isCollecting
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
index ed9a188..4144293d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
@@ -20,6 +20,8 @@
import android.util.AttributeSet
import android.view.LayoutInflater
import com.android.systemui.R
+import com.android.systemui.statusbar.StatusBarIconView.getVisibleStateString
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView
@@ -31,6 +33,15 @@
var subId: Int = -1
+ override fun toString(): String {
+ return "ModernStatusBarMobileView(" +
+ "slot='$slot', " +
+ "subId=$subId, " +
+ "isCollecting=${binding.isCollecting()}, " +
+ "visibleState=${getVisibleStateString(visibleState)}); " +
+ "viewString=${super.toString()}"
+ }
+
companion object {
/**
@@ -40,6 +51,7 @@
@JvmStatic
fun constructAndBind(
context: Context,
+ logger: MobileViewLogger,
slot: String,
viewModel: LocationBasedMobileViewModel,
): ModernStatusBarMobileView {
@@ -48,7 +60,8 @@
as ModernStatusBarMobileView)
.also {
it.subId = viewModel.subscriptionId
- it.initView(slot) { MobileIconBinder.bind(it, viewModel) }
+ it.initView(slot) { MobileIconBinder.bind(it, viewModel, logger) }
+ logger.logNewViewBinding(it, viewModel)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
index 8e103f7..f775940 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
@@ -19,6 +19,7 @@
import android.graphics.Color
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
/**
* A view model for an individual mobile icon that embeds the notion of a [StatusBarLocation]. This
@@ -26,11 +27,15 @@
*
* @param commonImpl for convenience, this class wraps a base interface that can provides all of the
* common implementations between locations. See [MobileIconViewModel]
+ * @property locationName the name of the location of this VM, used for logging.
+ * @property verboseLogger an optional logger to log extremely verbose view updates.
*/
abstract class LocationBasedMobileViewModel(
val commonImpl: MobileIconViewModelCommon,
statusBarPipelineFlags: StatusBarPipelineFlags,
debugTint: Int,
+ val locationName: String,
+ val verboseLogger: VerboseMobileViewLogger?,
) : MobileIconViewModelCommon by commonImpl {
val useDebugColoring: Boolean = statusBarPipelineFlags.useDebugColoring()
@@ -45,11 +50,16 @@
fun viewModelForLocation(
commonImpl: MobileIconViewModelCommon,
statusBarPipelineFlags: StatusBarPipelineFlags,
+ verboseMobileViewLogger: VerboseMobileViewLogger,
loc: StatusBarLocation,
): LocationBasedMobileViewModel =
when (loc) {
StatusBarLocation.HOME ->
- HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+ HomeMobileIconViewModel(
+ commonImpl,
+ statusBarPipelineFlags,
+ verboseMobileViewLogger,
+ )
StatusBarLocation.KEYGUARD ->
KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags)
StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
@@ -60,20 +70,41 @@
class HomeMobileIconViewModel(
commonImpl: MobileIconViewModelCommon,
statusBarPipelineFlags: StatusBarPipelineFlags,
+ verboseMobileViewLogger: VerboseMobileViewLogger,
) :
MobileIconViewModelCommon,
- LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.CYAN)
+ LocationBasedMobileViewModel(
+ commonImpl,
+ statusBarPipelineFlags,
+ debugTint = Color.CYAN,
+ locationName = "Home",
+ verboseMobileViewLogger,
+ )
class QsMobileIconViewModel(
commonImpl: MobileIconViewModelCommon,
statusBarPipelineFlags: StatusBarPipelineFlags,
) :
MobileIconViewModelCommon,
- LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.GREEN)
+ LocationBasedMobileViewModel(
+ commonImpl,
+ statusBarPipelineFlags,
+ debugTint = Color.GREEN,
+ locationName = "QS",
+ // Only do verbose logging for the Home location.
+ verboseLogger = null,
+ )
class KeyguardMobileIconViewModel(
commonImpl: MobileIconViewModelCommon,
statusBarPipelineFlags: StatusBarPipelineFlags,
) :
MobileIconViewModelCommon,
- LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.MAGENTA)
+ LocationBasedMobileViewModel(
+ commonImpl,
+ statusBarPipelineFlags,
+ debugTint = Color.MAGENTA,
+ locationName = "Keyguard",
+ // Only do verbose logging for the Home location.
+ verboseLogger = null,
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 0496278..dbb534b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -49,7 +49,7 @@
val contentDescription: Flow<ContentDescription>
val roaming: Flow<Boolean>
/** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
- val networkTypeIcon: Flow<Icon?>
+ val networkTypeIcon: Flow<Icon.Resource?>
val activityInVisible: Flow<Boolean>
val activityOutVisible: Flow<Boolean>
val activityContainerVisible: Flow<Boolean>
@@ -161,7 +161,7 @@
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- override val networkTypeIcon: Flow<Icon?> =
+ override val networkTypeIcon: Flow<Icon.Resource?> =
combine(
iconInteractor.networkTypeIconGroup,
showNetworkTypeIcon,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 8cb52af..2b90065 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -23,6 +23,8 @@
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
+import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import javax.inject.Inject
@@ -39,6 +41,8 @@
@Inject
constructor(
val subscriptionIdsFlow: StateFlow<List<Int>>,
+ val logger: MobileViewLogger,
+ private val verboseLogger: VerboseMobileViewLogger,
private val interactor: MobileIconsInteractor,
private val airplaneModeInteractor: AirplaneModeInteractor,
private val constants: ConnectivityConstants,
@@ -66,6 +70,7 @@
return LocationBasedMobileViewModel.viewModelForLocation(
common,
statusBarPipelineFlags,
+ verboseLogger,
location,
)
}
@@ -79,6 +84,8 @@
class Factory
@Inject
constructor(
+ private val logger: MobileViewLogger,
+ private val verboseLogger: VerboseMobileViewLogger,
private val interactor: MobileIconsInteractor,
private val airplaneModeInteractor: AirplaneModeInteractor,
private val constants: ConnectivityConstants,
@@ -88,6 +95,8 @@
fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
return MobileIconsViewModel(
subscriptionIdsFlow,
+ logger,
+ verboseLogger,
interactor,
airplaneModeInteractor,
constants,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
index 6f29e33..a96e8ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
@@ -38,11 +38,24 @@
int1 = network.getNetId()
str1 = networkCapabilities.toString()
},
- { "onCapabilitiesChanged[default=$bool1]: net=$int1 capabilities=$str1" }
+ { "on${if (bool1) "Default" else ""}CapabilitiesChanged: net=$int1 capabilities=$str1" }
)
}
- fun logOnLost(buffer: LogBuffer, tag: String, network: Network) {
- buffer.log(tag, LogLevel.INFO, { int1 = network.getNetId() }, { "onLost: net=$int1" })
+ fun logOnLost(
+ buffer: LogBuffer,
+ tag: String,
+ network: Network,
+ isDefaultNetworkCallback: Boolean,
+ ) {
+ buffer.log(
+ tag,
+ LogLevel.INFO,
+ {
+ int1 = network.getNetId()
+ bool1 = isDefaultNetworkCallback
+ },
+ { "on${if (bool1) "Default" else ""}Lost: net=$int1" }
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
index f67876b..81f8683 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
@@ -37,4 +37,7 @@
/** Notifies that the decor tint has been updated (used only for the dot). */
fun onDecorTintChanged(newTint: Int)
+
+ /** Returns true if the binding between the view and view-model is currently collecting. */
+ fun isCollecting(): Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
index b1e2812..1a13404 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -36,7 +36,7 @@
BaseStatusBarFrameLayout(context, attrs) {
private lateinit var slot: String
- private lateinit var binding: ModernStatusBarViewBinding
+ internal lateinit var binding: ModernStatusBarViewBinding
@StatusBarIconView.VisibleState
private var iconVisibleState: Int = STATE_HIDDEN
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index ee58160..b5e7b7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -128,6 +128,7 @@
}
override fun onLost(network: Network) {
+ logger.logOnLost(network, isDefaultNetworkCallback = true)
// The system no longer has a default network, so wifi is definitely not
// default.
trySend(false)
@@ -179,7 +180,7 @@
}
override fun onLost(network: Network) {
- logger.logOnLost(network)
+ logger.logOnLost(network, isDefaultNetworkCallback = false)
wifiNetworkChangeEvents.tryEmit(Unit)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
index a32e475..bb0b166 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
@@ -48,8 +48,8 @@
)
}
- fun logOnLost(network: Network) {
- LoggerHelper.logOnLost(buffer, TAG, network)
+ fun logOnLost(network: Network, isDefaultNetworkCallback: Boolean) {
+ LoggerHelper.logOnLost(buffer, TAG, network, isDefaultNetworkCallback)
}
fun logIntent(intentName: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 2aff12c..9e8c814 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -34,6 +34,7 @@
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -74,8 +75,12 @@
val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+ var isCollecting: Boolean = false
+
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
+ isCollecting = true
+
launch {
visibilityState.collect { visibilityState ->
groupView.isVisible = visibilityState == STATE_ICON
@@ -127,6 +132,12 @@
airplaneSpacer.isVisible = visible
}
}
+
+ try {
+ awaitCancellation()
+ } finally {
+ isCollecting = false
+ }
}
}
@@ -152,6 +163,10 @@
}
decorTint.value = newTint
}
+
+ override fun isCollecting(): Boolean {
+ return isCollecting
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index 7a73486..f23e102 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -21,6 +21,7 @@
import android.util.AttributeSet
import android.view.LayoutInflater
import com.android.systemui.R
+import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView
import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
@@ -33,6 +34,15 @@
context: Context,
attrs: AttributeSet?,
) : ModernStatusBarView(context, attrs) {
+
+ override fun toString(): String {
+ return "ModernStatusBarWifiView(" +
+ "slot='$slot', " +
+ "isCollecting=${binding.isCollecting()}, " +
+ "visibleState=${StatusBarIconView.getVisibleStateString(visibleState)}); " +
+ "viewString=${super.toString()}"
+ }
+
companion object {
/**
* Inflates a new instance of [ModernStatusBarWifiView], binds it to a view model, and
@@ -45,12 +55,9 @@
slot: String,
wifiViewModel: LocationBasedWifiViewModel,
): ModernStatusBarWifiView {
- return (
- LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
- as ModernStatusBarWifiView
- ).also {
- it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) }
- }
+ return (LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
+ as ModernStatusBarWifiView)
+ .also { it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) } }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index 7acdaff..01fabcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -22,13 +22,18 @@
import android.annotation.Nullable;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Trace;
-import android.util.Log;
+import android.util.IndentingPrintWriter;
+
+import androidx.annotation.NonNull;
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
+import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.wrapper.RotationPolicyWrapper;
+import java.io.PrintWriter;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -39,19 +44,19 @@
*/
@SysUISingleton
public final class DeviceStateRotationLockSettingController
- implements Listenable, RotationLockController.RotationLockControllerCallback {
-
- private static final String TAG = "DSRotateLockSettingCon";
+ implements Listenable, RotationLockController.RotationLockControllerCallback, Dumpable {
private final RotationPolicyWrapper mRotationPolicyWrapper;
private final DeviceStateManager mDeviceStateManager;
private final Executor mMainExecutor;
private final DeviceStateRotationLockSettingsManager mDeviceStateRotationLockSettingsManager;
+ private final DeviceStateRotationLockSettingControllerLogger mLogger;
// On registration for DeviceStateCallback, we will receive a callback with the current state
// and this will be initialized.
private int mDeviceState = -1;
- @Nullable private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
+ @Nullable
+ private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
private DeviceStateRotationLockSettingsManager.DeviceStateRotationLockSettingsListener
mDeviceStateRotationLockSettingsListener;
@@ -60,21 +65,27 @@
RotationPolicyWrapper rotationPolicyWrapper,
DeviceStateManager deviceStateManager,
@Main Executor executor,
- DeviceStateRotationLockSettingsManager deviceStateRotationLockSettingsManager) {
+ DeviceStateRotationLockSettingsManager deviceStateRotationLockSettingsManager,
+ DeviceStateRotationLockSettingControllerLogger logger,
+ DumpManager dumpManager) {
mRotationPolicyWrapper = rotationPolicyWrapper;
mDeviceStateManager = deviceStateManager;
mMainExecutor = executor;
mDeviceStateRotationLockSettingsManager = deviceStateRotationLockSettingsManager;
+ mLogger = logger;
+ dumpManager.registerDumpable(this);
}
@Override
public void setListening(boolean listening) {
+ mLogger.logListeningChange(listening);
if (listening) {
// Note that this is called once with the initial state of the device, even if there
// is no user action.
mDeviceStateCallback = this::updateDeviceState;
mDeviceStateManager.registerCallback(mMainExecutor, mDeviceStateCallback);
- mDeviceStateRotationLockSettingsListener = () -> readPersistedSetting(mDeviceState);
+ mDeviceStateRotationLockSettingsListener = () ->
+ readPersistedSetting("deviceStateRotationLockChange", mDeviceState);
mDeviceStateRotationLockSettingsManager.registerListener(
mDeviceStateRotationLockSettingsListener);
} else {
@@ -89,35 +100,28 @@
}
@Override
- public void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible) {
- if (mDeviceState == -1) {
- Log.wtf(TAG, "Device state was not initialized.");
+ public void onRotationLockStateChanged(boolean newRotationLocked, boolean affordanceVisible) {
+ int deviceState = mDeviceState;
+ boolean currentRotationLocked = mDeviceStateRotationLockSettingsManager
+ .isRotationLocked(deviceState);
+ mLogger.logRotationLockStateChanged(deviceState, newRotationLocked, currentRotationLocked);
+ if (deviceState == -1) {
return;
}
-
- if (rotationLocked
- == mDeviceStateRotationLockSettingsManager.isRotationLocked(mDeviceState)) {
- Log.v(TAG, "Rotation lock same as the current setting, no need to update.");
+ if (newRotationLocked == currentRotationLocked) {
return;
}
-
- saveNewRotationLockSetting(rotationLocked);
+ saveNewRotationLockSetting(newRotationLocked);
}
private void saveNewRotationLockSetting(boolean isRotationLocked) {
- Log.v(
- TAG,
- "saveNewRotationLockSetting [state="
- + mDeviceState
- + "] [isRotationLocked="
- + isRotationLocked
- + "]");
-
- mDeviceStateRotationLockSettingsManager.updateSetting(mDeviceState, isRotationLocked);
+ int deviceState = mDeviceState;
+ mLogger.logSaveNewRotationLockSetting(isRotationLocked, deviceState);
+ mDeviceStateRotationLockSettingsManager.updateSetting(deviceState, isRotationLocked);
}
private void updateDeviceState(int state) {
- Log.v(TAG, "updateDeviceState [state=" + state + "]");
+ mLogger.logUpdateDeviceState(mDeviceState, state);
if (Trace.isEnabled()) {
Trace.traceBegin(
Trace.TRACE_TAG_APP, "updateDeviceState [state=" + state + "]");
@@ -127,22 +131,26 @@
return;
}
- readPersistedSetting(state);
+ readPersistedSetting("updateDeviceState", state);
} finally {
Trace.endSection();
}
}
- private void readPersistedSetting(int state) {
+ private void readPersistedSetting(String caller, int state) {
int rotationLockSetting =
mDeviceStateRotationLockSettingsManager.getRotationLockSetting(state);
+ boolean shouldBeLocked = rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_LOCKED;
+ boolean isLocked = mRotationPolicyWrapper.isRotationLocked();
+
+ mLogger.readPersistedSetting(caller, state, rotationLockSetting, shouldBeLocked, isLocked);
+
if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
// This should not happen. Device states that have an ignored setting, should also
// specify a fallback device state which is not ignored.
// We won't handle this device state. The same rotation lock setting as before should
// apply and any changes to the rotation lock setting will be written for the previous
// valid device state.
- Log.w(TAG, "Missing fallback. Ignoring new device state: " + state);
return;
}
@@ -150,9 +158,18 @@
mDeviceState = state;
// Update the rotation policy, if needed, for this new device state
- boolean newRotationLockSetting = rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_LOCKED;
- if (newRotationLockSetting != mRotationPolicyWrapper.isRotationLocked()) {
- mRotationPolicyWrapper.setRotationLock(newRotationLockSetting);
+ if (shouldBeLocked != isLocked) {
+ mRotationPolicyWrapper.setRotationLock(shouldBeLocked);
}
}
+
+ @Override
+ public void dump(@NonNull PrintWriter printWriter, @NonNull String[] args) {
+ IndentingPrintWriter pw = new IndentingPrintWriter(printWriter);
+ mDeviceStateRotationLockSettingsManager.dump(pw);
+ pw.println("DeviceStateRotationLockSettingController");
+ pw.increaseIndent();
+ pw.println("mDeviceState: " + mDeviceState);
+ pw.decreaseIndent();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
new file mode 100644
index 0000000..aa502bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 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.policy
+
+import android.content.Context
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED
+import com.android.internal.R
+import com.android.systemui.log.dagger.DeviceStateAutoRotationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import javax.inject.Inject
+
+class DeviceStateRotationLockSettingControllerLogger
+@Inject
+constructor(@DeviceStateAutoRotationLog private val logBuffer: LogBuffer, context: Context) {
+
+ private val foldedStates = context.resources.getIntArray(R.array.config_foldedDeviceStates)
+ private val halfFoldedStates =
+ context.resources.getIntArray(R.array.config_halfFoldedDeviceStates)
+ private val unfoldedStates = context.resources.getIntArray(R.array.config_openDeviceStates)
+
+ fun logListeningChange(listening: Boolean) {
+ logBuffer.log(TAG, VERBOSE, { bool1 = listening }, { "setListening: $bool1" })
+ }
+
+ fun logRotationLockStateChanged(
+ state: Int,
+ newRotationLocked: Boolean,
+ currentRotationLocked: Boolean
+ ) {
+ logBuffer.log(
+ TAG,
+ VERBOSE,
+ {
+ int1 = state
+ bool1 = newRotationLocked
+ bool2 = currentRotationLocked
+ },
+ {
+ "onRotationLockStateChanged: " +
+ "state=$int1 [${int1.toDevicePostureString()}], " +
+ "newRotationLocked=$bool1, " +
+ "currentRotationLocked=$bool2"
+ }
+ )
+ }
+
+ fun logSaveNewRotationLockSetting(isRotationLocked: Boolean, state: Int) {
+ logBuffer.log(
+ TAG,
+ VERBOSE,
+ {
+ bool1 = isRotationLocked
+ int1 = state
+ },
+ { "saveNewRotationLockSetting: isRotationLocked=$bool1, state=$int1" }
+ )
+ }
+
+ fun logUpdateDeviceState(currentState: Int, newState: Int) {
+ logBuffer.log(
+ TAG,
+ VERBOSE,
+ {
+ int1 = currentState
+ int2 = newState
+ },
+ {
+ "updateDeviceState: " +
+ "current=$int1 [${int1.toDevicePostureString()}], " +
+ "new=$int2 [${int2.toDevicePostureString()}]"
+ }
+ )
+ }
+
+ fun readPersistedSetting(
+ caller: String,
+ state: Int,
+ rotationLockSetting: Int,
+ shouldBeLocked: Boolean,
+ isLocked: Boolean
+ ) {
+ logBuffer.log(
+ TAG,
+ VERBOSE,
+ {
+ str1 = caller
+ int1 = state
+ int2 = rotationLockSetting
+ bool1 = shouldBeLocked
+ bool2 = isLocked
+ },
+ {
+ "readPersistedSetting: " +
+ "caller=$str1, " +
+ "state=$int1 [${int1.toDevicePostureString()}], " +
+ "rotationLockSettingForState: ${int2.toRotationLockSettingString()}, " +
+ "shouldBeLocked=$bool1, " +
+ "isLocked=$bool2"
+ }
+ )
+ }
+
+ private fun Int.toDevicePostureString(): String {
+ return when (this) {
+ in foldedStates -> "Folded"
+ in unfoldedStates -> "Unfolded"
+ in halfFoldedStates -> "Half-Folded"
+ -1 -> "Uninitialized"
+ else -> "Unknown"
+ }
+ }
+}
+
+private fun Int.toRotationLockSettingString(): String {
+ return when (this) {
+ DEVICE_STATE_ROTATION_LOCK_IGNORED -> "IGNORED"
+ DEVICE_STATE_ROTATION_LOCK_LOCKED -> "LOCKED"
+ DEVICE_STATE_ROTATION_LOCK_UNLOCKED -> "UNLOCKED"
+ else -> "Unknown"
+ }
+}
+
+private const val TAG = "DSRotateLockSettingCon"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index 853de7b..bcf3b0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.window;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
-import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
+import static android.view.WindowInsets.Type.mandatorySystemGestures;
+import static android.view.WindowInsets.Type.statusBars;
+import static android.view.WindowInsets.Type.tappableElement;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
@@ -85,6 +85,7 @@
private final ViewGroup mLaunchAnimationContainer;
private WindowManager.LayoutParams mLp;
private final WindowManager.LayoutParams mLpChanged;
+ private final Binder mInsetsSourceOwner = new Binder();
@Inject
public StatusBarWindowController(
@@ -231,16 +232,16 @@
lp.packageName = mContext.getPackageName();
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
final InsetsFrameProvider gestureInsetsProvider =
- new InsetsFrameProvider(ITYPE_TOP_MANDATORY_GESTURES);
+ new InsetsFrameProvider(mInsetsSourceOwner, 0, mandatorySystemGestures());
final int safeTouchRegionHeight = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.display_cutout_touchable_region_size);
if (safeTouchRegionHeight > 0) {
- gestureInsetsProvider.minimalInsetsSizeInDisplayCutoutSafe =
- Insets.of(0, safeTouchRegionHeight, 0, 0);
+ gestureInsetsProvider.setMinimalInsetsSizeInDisplayCutoutSafe(
+ Insets.of(0, safeTouchRegionHeight, 0, 0));
}
lp.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(ITYPE_STATUS_BAR),
- new InsetsFrameProvider(ITYPE_TOP_TAPPABLE_ELEMENT),
+ new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()),
+ new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()),
gestureInsetsProvider
};
return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 2ad3558..faae8df 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -29,6 +29,7 @@
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_SOURCE;
import static com.android.systemui.theme.ThemeOverlayApplier.TIMESTAMP_FIELD;
+import android.app.UiModeManager;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.app.WallpaperManager.OnColorsChangedListener;
@@ -54,7 +55,6 @@
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
-import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -140,8 +140,8 @@
private boolean mNeedsOverlayCreation;
// Dominant color extracted from wallpaper, NOT the color used on the overlay
protected int mMainWallpaperColor = Color.TRANSPARENT;
- // UI contrast as reported by AccessibilityManager
- private float mUiContrast = 0;
+ // UI contrast as reported by UiModeManager
+ private float mContrast = 0;
// Theme variant: Vibrant, Tonal, Expressive, etc
@VisibleForTesting
protected Style mThemeStyle = Style.TONAL_SPOT;
@@ -158,7 +158,7 @@
private final SparseArray<WallpaperColors> mDeferredWallpaperColors = new SparseArray<>();
private final SparseIntArray mDeferredWallpaperColorsFlags = new SparseIntArray();
private final WakefulnessLifecycle mWakefulnessLifecycle;
- private final AccessibilityManager mAccessibilityManager;
+ private final UiModeManager mUiModeManager;
private DynamicScheme mDynamicSchemeDark;
private DynamicScheme mDynamicSchemeLight;
@@ -392,7 +392,7 @@
FeatureFlags featureFlags,
@Main Resources resources,
WakefulnessLifecycle wakefulnessLifecycle,
- AccessibilityManager accessibilityManager) {
+ UiModeManager uiModeManager) {
mContext = context;
mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME);
mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
@@ -408,7 +408,7 @@
mUserTracker = userTracker;
mResources = resources;
mWakefulnessLifecycle = wakefulnessLifecycle;
- mAccessibilityManager = accessibilityManager;
+ mUiModeManager = uiModeManager;
dumpManager.registerDumpable(TAG, this);
}
@@ -445,9 +445,9 @@
}
},
UserHandle.USER_ALL);
- mUiContrast = mAccessibilityManager.getUiContrast();
- mAccessibilityManager.addUiContrastChangeListener(mMainExecutor, uiContrast -> {
- mUiContrast = uiContrast;
+ mContrast = mUiModeManager.getContrast();
+ mUiModeManager.addContrastChangeListener(mMainExecutor, contrast -> {
+ mContrast = contrast;
// Force reload so that we update even when the main color has not changed
reevaluateSystemTheme(true /* forceReload */);
});
@@ -586,9 +586,9 @@
mSecondaryOverlay = createAccentOverlay();
mDynamicSchemeDark = dynamicSchemeFromStyle(
- mThemeStyle, color, true /* isDark */, mUiContrast);
+ mThemeStyle, color, true /* isDark */, mContrast);
mDynamicSchemeLight = dynamicSchemeFromStyle(
- mThemeStyle, color, false /* isDark */, mUiContrast);
+ mThemeStyle, color, false /* isDark */, mContrast);
mDynamicOverlay = createDynamicOverlay();
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 433642b..94dd1b3 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -33,6 +33,8 @@
import android.util.Log
import com.android.internal.logging.UiEventLogger
import com.android.internal.util.UserIcons
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.R
import com.android.systemui.SystemUISecondaryUserService
import com.android.systemui.animation.Expandable
@@ -93,6 +95,7 @@
@Application private val applicationScope: CoroutineScope,
telephonyInteractor: TelephonyInteractor,
broadcastDispatcher: BroadcastDispatcher,
+ keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val activityManager: ActivityManager,
private val refreshUsersScheduler: RefreshUsersScheduler,
@@ -290,6 +293,12 @@
val isSimpleUserSwitcher: Boolean
get() = repository.isSimpleUserSwitcher()
+ val keyguardUpdateMonitorCallback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onKeyguardGoingAway() {
+ dismissDialog()
+ }
+ }
init {
refreshUsersScheduler.refreshIfNotPaused()
@@ -320,6 +329,7 @@
onBroadcastReceived(intent, previousSelectedUser)
}
.launchIn(applicationScope)
+ keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
}
fun addCallback(callback: UserCallback) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 94ce002..95cc12a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1508,6 +1508,7 @@
mHandler.removeMessages(H.SHOW);
if (mIsAnimatingDismiss) {
Log.d(TAG, "dismissH: isAnimatingDismiss");
+ Trace.endSection();
return;
}
mIsAnimatingDismiss = true;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 064bc9c..38d3a3e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -521,6 +521,38 @@
}
@Test
+ public void testWillRunDismissFromKeyguardIsTrue() {
+ ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class);
+ when(action.willRunAnimationOnKeyguard()).thenReturn(true);
+ mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */);
+
+ mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
+
+ assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isTrue();
+ }
+
+ @Test
+ public void testWillRunDismissFromKeyguardIsFalse() {
+ ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class);
+ when(action.willRunAnimationOnKeyguard()).thenReturn(false);
+ mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */);
+
+ mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
+
+ assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse();
+ }
+
+ @Test
+ public void testWillRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() {
+ mKeyguardSecurityContainerController.setOnDismissAction(null /* action */,
+ null /* cancelAction */);
+
+ mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
+
+ assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse();
+ }
+
+ @Test
public void testOnStartingToHide() {
mKeyguardSecurityContainerController.onStartingToHide();
verify(mInputViewController).onStartingToHide();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 531006d..565fc57 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -263,9 +263,6 @@
assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID);
assertThat(userSwitcherConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
- assertThat(userSwitcherConstraint.layout.bottomMargin).isEqualTo(
- getContext().getResources().getDimensionPixelSize(
- R.dimen.bouncer_user_switcher_y_trans));
assertThat(viewFlipperConstraint.layout.horizontalChainStyle).isEqualTo(CHAIN_SPREAD);
assertThat(userSwitcherConstraint.layout.horizontalChainStyle).isEqualTo(CHAIN_SPREAD);
assertThat(viewFlipperConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
index f01da2d..8a5c5b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
@@ -61,7 +61,7 @@
val interp = FontInterpolator()
assertSameAxes(startFont, interp.lerp(startFont, endFont, 0f))
assertSameAxes(endFont, interp.lerp(startFont, endFont, 1f))
- assertSameAxes("'wght' 500, 'ital' 0.5, 'GRAD' 450", interp.lerp(startFont, endFont, 0.5f))
+ assertSameAxes("'wght' 496, 'ital' 0.5, 'GRAD' 450", interp.lerp(startFont, endFont, 0.5f))
}
@Test
@@ -74,7 +74,7 @@
.build()
val interp = FontInterpolator()
- assertSameAxes("'wght' 250, 'ital' 0.5", interp.lerp(startFont, endFont, 0.5f))
+ assertSameAxes("'wght' 249, 'ital' 0.5", interp.lerp(startFont, endFont, 0.5f))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
index 3503902..9d16185 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
@@ -45,8 +45,8 @@
public class BroadcastDialogTest extends SysuiTestCase {
private static final String CURRENT_BROADCAST_APP = "Music";
- private static final String SWITCH_APP = "Files by Google";
- private static final String TEST_PACKAGE = "com.google.android.apps.nbu.files";
+ private static final String SWITCH_APP = "System UI";
+ private static final String TEST_PACKAGE = "com.android.systemui";
private BroadcastDialog mBroadcastDialog;
private View mDialogView;
private TextView mTitle;
@@ -59,6 +59,7 @@
MockitoAnnotations.initMocks(this);
mBroadcastDialog = new BroadcastDialog(mContext, mock(MediaOutputDialogFactory.class),
CURRENT_BROADCAST_APP, TEST_PACKAGE, mock(UiEventLogger.class));
+
mBroadcastDialog.show();
mDialogView = mBroadcastDialog.mDialogView;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index c2fb904..ffd75fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -35,6 +35,7 @@
import static org.mockito.Mockito.when;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.app.RemoteAction;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -101,6 +102,9 @@
private ArgumentCaptor<ClipboardOverlayView.ClipboardOverlayCallbacks> mOverlayCallbacksCaptor;
private ClipboardOverlayView.ClipboardOverlayCallbacks mCallbacks;
+ @Captor
+ private ArgumentCaptor<AnimatorListenerAdapter> mAnimatorArgumentCaptor;
+
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@Before
@@ -478,12 +482,16 @@
when(mClipboardOverlayWindow.getWindowInsets()).thenReturn(
getImeInsets(new Rect(0, 0, 0, 1)));
mOverlayController.setClipData(mSampleClipData, "");
+ Animator mockFadeoutAnimator = Mockito.mock(Animator.class);
+ when(mClipboardOverlayView.getMinimizedFadeoutAnimation()).thenReturn(mockFadeoutAnimator);
verify(mClipboardOverlayView).setMinimized(true);
verify(mClipboardOverlayView, never()).setMinimized(false);
verify(mClipboardOverlayView, never()).showTextPreview(any(), anyBoolean());
mCallbacks.onMinimizedViewTapped();
+ verify(mockFadeoutAnimator).addListener(mAnimatorArgumentCaptor.capture());
+ mAnimatorArgumentCaptor.getValue().onAnimationEnd(mockFadeoutAnimator);
verify(mClipboardOverlayView).setMinimized(false);
verify(mClipboardOverlayView).showTextPreview("Test Item", false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
index eafe727..afd9be5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
@@ -107,4 +107,32 @@
assertThat(mSeekbar.getProgress()).isEqualTo(0);
}
+
+ @Test
+ public void setProgressStateLabels_getExpectedStateDescriptionOnInitialization() {
+ String[] stateLabels = new String[]{"1", "2", "3", "4", "5"};
+ mIconDiscreteSliderLinearLayout.setMax(stateLabels.length);
+ mIconDiscreteSliderLinearLayout.setProgress(1);
+ mIconDiscreteSliderLinearLayout.setProgressStateLabels(stateLabels);
+
+ final int currentProgress = mSeekbar.getProgress();
+ final CharSequence stateDescription = mSeekbar.getStateDescription();
+
+ assertThat(currentProgress).isEqualTo(1);
+ assertThat(stateDescription).isEqualTo(stateLabels[currentProgress]);
+ }
+
+ @Test
+ public void setProgressStateLabels_progressChanged_getExpectedStateDescription() {
+ String[] stateLabels = new String[]{"1", "2", "3", "4", "5"};
+ mIconDiscreteSliderLinearLayout.setMax(stateLabels.length);
+ mIconDiscreteSliderLinearLayout.setProgressStateLabels(stateLabels);
+ mIconDiscreteSliderLinearLayout.setProgress(1);
+
+ final int currentProgress = mSeekbar.getProgress();
+ final CharSequence stateDescription = mSeekbar.getStateDescription();
+
+ assertThat(currentProgress).isEqualTo(1);
+ assertThat(stateDescription).isEqualTo(stateLabels[currentProgress]);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index 28e80057..c98d537 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.FakeSelectedComponentRepository
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dump.DumpManager
import com.android.systemui.settings.UserFileManager
@@ -56,7 +57,6 @@
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.inOrder
@@ -66,6 +66,7 @@
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import java.io.File
import java.util.*
@@ -108,6 +109,8 @@
private lateinit var listingCallbackCaptor:
ArgumentCaptor<ControlsListingController.ControlsListingCallback>
+ private val preferredPanelRepository = FakeSelectedComponentRepository()
+
private lateinit var delayableExecutor: FakeExecutor
private lateinit var controller: ControlsControllerImpl
private lateinit var canceller: DidRunRunnable
@@ -168,6 +171,7 @@
wrapper,
delayableExecutor,
uiController,
+ preferredPanelRepository,
bindingController,
listingController,
userFileManager,
@@ -221,6 +225,7 @@
mContext,
delayableExecutor,
uiController,
+ preferredPanelRepository,
bindingController,
listingController,
userFileManager,
@@ -240,6 +245,7 @@
mContext,
delayableExecutor,
uiController,
+ preferredPanelRepository,
bindingController,
listingController,
userFileManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
index 7ac1953..272f589 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
@@ -22,6 +22,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.FakeSharedPreferences
@@ -40,6 +42,8 @@
@Mock private lateinit var userTracker: UserTracker
+ private val featureFlags = FakeFeatureFlags()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -48,6 +52,7 @@
arrayOf<String>()
)
whenever(userTracker.userId).thenReturn(0)
+ featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
}
@Test
@@ -127,8 +132,25 @@
assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty()
}
+ @Test
+ fun testSetAuthorizedPackageAfterFeatureDisabled() {
+ mContext.orCreateTestableResources.addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf(TEST_PACKAGE)
+ )
+ val sharedPrefs = FakeSharedPreferences()
+ val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
+ val repository = createRepository(fileManager)
+
+ repository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
+
+ featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
+
+ assertThat(repository.getAuthorizedPanels()).isEqualTo(setOf(TEST_PACKAGE))
+ }
+
private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl {
- return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker)
+ return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker, featureFlags)
}
private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
new file mode 100644
index 0000000..a7677cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.controls.panels
+
+class FakeSelectedComponentRepository : SelectedComponentRepository {
+
+ private var selectedComponent: SelectedComponentRepository.SelectedComponent? = null
+ private var shouldAddDefaultPanel: Boolean = true
+
+ override fun getSelectedComponent(): SelectedComponentRepository.SelectedComponent? =
+ selectedComponent
+
+ override fun setSelectedComponent(
+ selectedComponent: SelectedComponentRepository.SelectedComponent
+ ) {
+ this.selectedComponent = selectedComponent
+ }
+
+ override fun removeSelectedComponent() {
+ selectedComponent = null
+ }
+
+ override fun shouldAddDefaultComponent(): Boolean = shouldAddDefaultPanel
+
+ override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
+ shouldAddDefaultPanel = shouldAdd
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
new file mode 100644
index 0000000..0c7b9cb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2023 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.controls.panels
+
+import android.content.ComponentName
+import android.content.SharedPreferences
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class SelectedComponentRepositoryTest : SysuiTestCase() {
+
+ private companion object {
+ val COMPONENT_A =
+ SelectedComponentRepository.SelectedComponent(
+ name = "a",
+ componentName = ComponentName.unflattenFromString("pkg/.cls_a"),
+ isPanel = false,
+ )
+ val COMPONENT_B =
+ SelectedComponentRepository.SelectedComponent(
+ name = "b",
+ componentName = ComponentName.unflattenFromString("pkg/.cls_b"),
+ isPanel = false,
+ )
+ }
+
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var userFileManager: UserFileManager
+
+ private val featureFlags = FakeFeatureFlags()
+ private val sharedPreferences: SharedPreferences = FakeSharedPreferences()
+
+ // under test
+ private lateinit var repository: SelectedComponentRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(userFileManager.getSharedPreferences(any(), any(), any()))
+ .thenReturn(sharedPreferences)
+
+ repository = SelectedComponentRepositoryImpl(userFileManager, userTracker, featureFlags)
+ }
+
+ @Test
+ fun testUnsetIsNull() {
+ assertThat(repository.getSelectedComponent()).isNull()
+ }
+
+ @Test
+ fun testGetReturnsSet() {
+ repository.setSelectedComponent(COMPONENT_A)
+
+ assertThat(repository.getSelectedComponent()).isEqualTo(COMPONENT_A)
+ }
+
+ @Test
+ fun testSetOverrides() {
+ repository.setSelectedComponent(COMPONENT_A)
+ repository.setSelectedComponent(COMPONENT_B)
+
+ assertThat(repository.getSelectedComponent()).isEqualTo(COMPONENT_B)
+ }
+
+ @Test
+ fun testRemove() {
+ repository.setSelectedComponent(COMPONENT_A)
+
+ repository.removeSelectedComponent()
+
+ assertThat(repository.getSelectedComponent()).isNull()
+ }
+
+ @Test
+ fun testFeatureEnabled_shouldAddDefaultPanelDefaultsToTrue() {
+ featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
+
+ assertThat(repository.shouldAddDefaultComponent()).isTrue()
+ }
+
+ @Test
+ fun testFeatureDisabled_shouldAddDefaultPanelDefaultsToTrue() {
+ featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
+
+ assertThat(repository.shouldAddDefaultComponent()).isTrue()
+ }
+
+ @Test
+ fun testFeatureEnabled_shouldAddDefaultPanelChecked() {
+ featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
+ repository.setShouldAddDefaultComponent(false)
+
+ assertThat(repository.shouldAddDefaultComponent()).isFalse()
+ }
+
+ @Test
+ fun testFeatureDisabled_shouldAlwaysAddDefaultPanelAlwaysTrue() {
+ featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
+ repository.setShouldAddDefaultComponent(false)
+
+ assertThat(repository.shouldAddDefaultComponent()).isTrue()
+ }
+
+ @Test
+ fun testGetPreferredStructure_differentUserId() {
+ sharedPreferences.savePanel(COMPONENT_A)
+ whenever(
+ userFileManager.getSharedPreferences(
+ DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+ 0,
+ 1,
+ )
+ )
+ .thenReturn(FakeSharedPreferences().also { it.savePanel(COMPONENT_B) })
+
+ val previousPreferredStructure = repository.getSelectedComponent()
+ whenever(userTracker.userId).thenReturn(1)
+ val currentPreferredStructure = repository.getSelectedComponent()
+
+ assertThat(previousPreferredStructure).isEqualTo(COMPONENT_A)
+ assertThat(currentPreferredStructure).isNotEqualTo(previousPreferredStructure)
+ assertThat(currentPreferredStructure).isEqualTo(COMPONENT_B)
+ }
+
+ private fun SharedPreferences.savePanel(panel: SelectedComponentRepository.SelectedComponent) {
+ edit()
+ .putString("controls_component", panel.componentName?.flattenToString())
+ .putString("controls_structure", panel.name)
+ .putBoolean("controls_is_panel", panel.isPanel)
+ .commit()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
index 7ecaca6..9d8084d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
@@ -23,17 +23,19 @@
import android.content.pm.ServiceInfo
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.FakeSelectedComponentRepository
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import java.util.Optional
import org.junit.Before
@@ -53,16 +55,16 @@
@Mock private lateinit var controlsController: ControlsController
@Mock private lateinit var controlsListingController: ControlsListingController
@Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
+
+ private val preferredPanelsRepository = FakeSelectedComponentRepository()
private lateinit var fakeExecutor: FakeExecutor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- context.orCreateTestableResources.addOverride(
- R.array.config_controlsPreferredPackages,
- arrayOf<String>()
- )
+ whenever(authorizedPanelsRepository.getPreferredPackages()).thenReturn(setOf())
fakeExecutor = FakeExecutor(FakeSystemClock())
}
@@ -87,10 +89,8 @@
@Test
fun testPreferredPackagesNotInstalled_noNewSelection() {
- context.orCreateTestableResources.addOverride(
- R.array.config_controlsPreferredPackages,
- arrayOf(TEST_PACKAGE_PANEL)
- )
+ whenever(authorizedPanelsRepository.getPreferredPackages())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
`when`(controlsListingController.getCurrentServices()).thenReturn(emptyList())
@@ -101,10 +101,8 @@
@Test
fun testPreferredPackageNotPanel_noNewSelection() {
- context.orCreateTestableResources.addOverride(
- R.array.config_controlsPreferredPackages,
- arrayOf(TEST_PACKAGE_PANEL)
- )
+ whenever(authorizedPanelsRepository.getPreferredPackages())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "not panel", hasPanel = false))
`when`(controlsListingController.getCurrentServices()).thenReturn(listings)
@@ -116,10 +114,8 @@
@Test
fun testExistingSelection_noNewSelection() {
- context.orCreateTestableResources.addOverride(
- R.array.config_controlsPreferredPackages,
- arrayOf(TEST_PACKAGE_PANEL)
- )
+ whenever(authorizedPanelsRepository.getPreferredPackages())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection())
.thenReturn(mock<SelectedItem.PanelItem>())
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
@@ -132,10 +128,8 @@
@Test
fun testPanelAdded() {
- context.orCreateTestableResources.addOverride(
- R.array.config_controlsPreferredPackages,
- arrayOf(TEST_PACKAGE_PANEL)
- )
+ whenever(authorizedPanelsRepository.getPreferredPackages())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
`when`(controlsListingController.getCurrentServices()).thenReturn(listings)
@@ -147,10 +141,8 @@
@Test
fun testMultiplePreferredOnlyOnePanel_panelAdded() {
- context.orCreateTestableResources.addOverride(
- R.array.config_controlsPreferredPackages,
- arrayOf("other_package", TEST_PACKAGE_PANEL)
- )
+ whenever(authorizedPanelsRepository.getPreferredPackages())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings =
listOf(
@@ -166,10 +158,8 @@
@Test
fun testMultiplePreferredMultiplePanels_firstPreferredAdded() {
- context.orCreateTestableResources.addOverride(
- R.array.config_controlsPreferredPackages,
- arrayOf(TEST_PACKAGE_PANEL, "other_package")
- )
+ whenever(authorizedPanelsRepository.getPreferredPackages())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings =
listOf(
@@ -217,6 +207,20 @@
verify(controlsController, never()).bindComponentForPanel(any())
}
+ @Test
+ fun testAlreadyAddedPanel_noNewSelection() {
+ preferredPanelsRepository.setShouldAddDefaultComponent(false)
+ whenever(authorizedPanelsRepository.getPreferredPackages())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
+ `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController, never()).setPreferredSelection(any())
+ }
+
private fun createStartable(enabled: Boolean): ControlsStartable {
val component: ControlsComponent =
mock() {
@@ -230,7 +234,13 @@
`when`(getControlsListingController()).thenReturn(Optional.empty())
}
}
- return ControlsStartable(context.resources, fakeExecutor, component, userTracker)
+ return ControlsStartable(
+ fakeExecutor,
+ component,
+ userTracker,
+ authorizedPanelsRepository,
+ preferredPanelsRepository,
+ )
}
private fun ControlsServiceInfo(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 23faa99..3f61bf7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -42,16 +42,14 @@
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.FakeSelectedComponentRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
-import com.android.systemui.shade.ShadeController
-import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.FakeSystemUIDialogController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -64,20 +62,18 @@
import com.android.wm.shell.TaskView
import com.android.wm.shell.TaskViewFactory
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import java.util.function.Consumer
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.anyString
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.Optional
-import java.util.function.Consumer
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -87,11 +83,9 @@
@Mock lateinit var controlsListingController: ControlsListingController
@Mock lateinit var controlActionCoordinator: ControlActionCoordinator
@Mock lateinit var activityStarter: ActivityStarter
- @Mock lateinit var shadeController: ShadeController
@Mock lateinit var iconCache: CustomIconCache
@Mock lateinit var controlsMetricsLogger: ControlsMetricsLogger
@Mock lateinit var keyguardStateController: KeyguardStateController
- @Mock lateinit var userFileManager: UserFileManager
@Mock lateinit var userTracker: UserTracker
@Mock lateinit var taskViewFactory: TaskViewFactory
@Mock lateinit var dumpManager: DumpManager
@@ -99,7 +93,7 @@
@Mock lateinit var featureFlags: FeatureFlags
@Mock lateinit var packageManager: PackageManager
- private val sharedPreferences = FakeSharedPreferences()
+ private val preferredPanelRepository = FakeSelectedComponentRepository()
private val fakeDialogController = FakeSystemUIDialogController()
private val uiExecutor = FakeExecutor(FakeSystemClock())
private val bgExecutor = FakeExecutor(FakeSystemClock())
@@ -138,94 +132,30 @@
iconCache,
controlsMetricsLogger,
keyguardStateController,
- userFileManager,
userTracker,
Optional.of(taskViewFactory),
controlsSettingsRepository,
authorizedPanelsRepository,
+ preferredPanelRepository,
featureFlags,
ControlsDialogsFactory { fakeDialogController.dialog },
dumpManager,
)
- `when`(
- userFileManager.getSharedPreferences(
- DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
- 0,
- 0
- )
- )
- .thenReturn(sharedPreferences)
- `when`(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
- .thenReturn(sharedPreferences)
`when`(userTracker.userId).thenReturn(0)
`when`(userTracker.userHandle).thenReturn(UserHandle.of(0))
}
@Test
- fun testGetPreferredStructure() {
- val structureInfo = mock<StructureInfo>()
- underTest.getPreferredSelectedItem(listOf(structureInfo))
- verify(userFileManager)
- .getSharedPreferences(
- fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
- mode = 0,
- userId = 0
- )
- }
-
- @Test
- fun testGetPreferredStructure_differentUserId() {
- val selectedItems =
- listOf(
- SelectedItem.StructureItem(
- StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
- ),
- SelectedItem.StructureItem(
- StructureInfo(ComponentName.unflattenFromString("pkg/.cls2"), "b", ArrayList())
- ),
- )
- val structures = selectedItems.map { it.structure }
- sharedPreferences
- .edit()
- .putString("controls_component", selectedItems[0].componentName.flattenToString())
- .putString("controls_structure", selectedItems[0].name.toString())
- .commit()
-
- val differentSharedPreferences = FakeSharedPreferences()
- differentSharedPreferences
- .edit()
- .putString("controls_component", selectedItems[1].componentName.flattenToString())
- .putString("controls_structure", selectedItems[1].name.toString())
- .commit()
-
- val previousPreferredStructure = underTest.getPreferredSelectedItem(structures)
-
- `when`(
- userFileManager.getSharedPreferences(
- DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
- 0,
- 1
- )
- )
- .thenReturn(differentSharedPreferences)
- `when`(userTracker.userId).thenReturn(1)
-
- val currentPreferredStructure = underTest.getPreferredSelectedItem(structures)
-
- assertThat(previousPreferredStructure).isEqualTo(selectedItems[0])
- assertThat(currentPreferredStructure).isEqualTo(selectedItems[1])
- assertThat(currentPreferredStructure).isNotEqualTo(previousPreferredStructure)
- }
-
- @Test
fun testGetPreferredPanel() {
val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
- sharedPreferences
- .edit()
- .putString("controls_component", panel.componentName.flattenToString())
- .putString("controls_structure", panel.appName.toString())
- .putBoolean("controls_is_panel", true)
- .commit()
+
+ preferredPanelRepository.setSelectedComponent(
+ SelectedComponentRepository.SelectedComponent(
+ name = panel.appName.toString(),
+ componentName = panel.componentName,
+ isPanel = true,
+ )
+ )
val selected = underTest.getPreferredSelectedItem(emptyList())
@@ -369,11 +299,9 @@
StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
),
)
- sharedPreferences
- .edit()
- .putString("controls_component", selectedItems[0].componentName.flattenToString())
- .putString("controls_structure", selectedItems[0].name.toString())
- .commit()
+ preferredPanelRepository.setSelectedComponent(
+ SelectedComponentRepository.SelectedComponent(selectedItems[0])
+ )
assertThat(underTest.resolveActivity())
.isEqualTo(ControlsProviderSelectorActivity::class.java)
@@ -418,12 +346,9 @@
val componentName = ComponentName(context, "cls")
whenever(controlsController.removeFavorites(eq(componentName))).thenReturn(true)
val panel = SelectedItem.PanelItem("App name", componentName)
- sharedPreferences
- .edit()
- .putString("controls_component", panel.componentName.flattenToString())
- .putString("controls_structure", panel.appName.toString())
- .putBoolean("controls_is_panel", true)
- .commit()
+ preferredPanelRepository.setSelectedComponent(
+ SelectedComponentRepository.SelectedComponent(panel)
+ )
underTest.show(parent, {}, context)
underTest.startRemovingApp(componentName, "Test App")
@@ -432,11 +357,8 @@
verify(controlsController).removeFavorites(eq(componentName))
assertThat(underTest.getPreferredSelectedItem(emptyList()))
.isEqualTo(SelectedItem.EMPTY_SELECTION)
- with(sharedPreferences) {
- assertThat(contains("controls_component")).isFalse()
- assertThat(contains("controls_structure")).isFalse()
- assertThat(contains("controls_is_panel")).isFalse()
- }
+ assertThat(preferredPanelRepository.shouldAddDefaultComponent()).isFalse()
+ assertThat(preferredPanelRepository.getSelectedComponent()).isNull()
}
@Test
@@ -452,12 +374,9 @@
private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
val activity = ComponentName(context, "activity")
- sharedPreferences
- .edit()
- .putString("controls_component", panel.componentName.flattenToString())
- .putString("controls_structure", panel.appName.toString())
- .putBoolean("controls_is_panel", true)
- .commit()
+ preferredPanelRepository.setSelectedComponent(
+ SelectedComponentRepository.SelectedComponent(panel)
+ )
return ControlsServiceInfo(panel.componentName, panel.appName, activity)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 6c23254..0a94706 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -1,6 +1,8 @@
package com.android.systemui.dreams
+import android.animation.Animator
import android.animation.AnimatorSet
+import android.animation.ValueAnimator
import android.testing.AndroidTestingRunner
import android.view.View
import androidx.test.filters.SmallTest
@@ -10,13 +12,16 @@
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.eq
@@ -71,6 +76,19 @@
}
@Test
+ fun testExitAnimationUpdatesState() {
+ controller.startExitAnimations(animatorBuilder = { mockAnimator })
+
+ verify(stateController).setExitAnimationsRunning(true)
+
+ val captor = argumentCaptor<Animator.AnimatorListener>()
+ verify(mockAnimator).addListener(captor.capture())
+
+ captor.value.onAnimationEnd(mockAnimator)
+ verify(stateController).setExitAnimationsRunning(false)
+ }
+
+ @Test
fun testWakeUpCallsExecutor() {
val mockExecutor: DelayableExecutor = mock()
val mockCallback: Runnable = mock()
@@ -87,7 +105,7 @@
fun testWakeUpAfterStartWillCancel() {
val mockStartAnimator: AnimatorSet = mock()
- controller.startEntryAnimations(animatorBuilder = { mockStartAnimator })
+ controller.startEntryAnimations(false, animatorBuilder = { mockStartAnimator })
verify(mockStartAnimator, never()).cancel()
@@ -100,4 +118,50 @@
// animator.
verify(mockStartAnimator, times(1)).cancel()
}
+
+ @Test
+ fun testEntryAnimations_translatesUpwards() {
+ val mockStartAnimator: AnimatorSet = mock()
+
+ controller.startEntryAnimations(
+ /* downwards= */ false,
+ animatorBuilder = { mockStartAnimator }
+ )
+
+ val animatorCaptor = ArgumentCaptor.forClass(Animator::class.java)
+ verify(mockStartAnimator).playTogether(animatorCaptor.capture())
+
+ // Check if there's a ValueAnimator starting at the expected Y distance.
+ val animators: List<ValueAnimator> = animatorCaptor.allValues as List<ValueAnimator>
+ assertTrue(
+ animators.any {
+ // Call setCurrentFraction so the animated value jumps to the initial value.
+ it.setCurrentFraction(0f)
+ it.animatedValue == DREAM_IN_TRANSLATION_Y_DISTANCE.toFloat()
+ }
+ )
+ }
+
+ @Test
+ fun testEntryAnimations_translatesDownwards() {
+ val mockStartAnimator: AnimatorSet = mock()
+
+ controller.startEntryAnimations(
+ /* downwards= */ true,
+ animatorBuilder = { mockStartAnimator }
+ )
+
+ val animatorCaptor = ArgumentCaptor.forClass(Animator::class.java)
+ verify(mockStartAnimator).playTogether(animatorCaptor.capture())
+
+ // Check if there's a ValueAnimator starting at the expected Y distance.
+ val animators: List<ValueAnimator> = animatorCaptor.allValues as List<ValueAnimator>
+ assertTrue(
+ animators.any {
+ // Call setCurrentFraction so the animated value jumps to the initial value.
+ it.setCurrentFraction(0f)
+ it.animatedValue == -DREAM_IN_TRANSLATION_Y_DISTANCE.toFloat()
+ }
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 6b095ff..2a72e7d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.dreams;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -33,6 +34,7 @@
import androidx.test.filters.SmallTest;
+import com.android.dream.lowlight.LowLightTransitionCoordinator;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.complication.ComplicationHostViewController;
@@ -65,6 +67,9 @@
DreamOverlayStatusBarViewController mDreamOverlayStatusBarViewController;
@Mock
+ LowLightTransitionCoordinator mLowLightTransitionCoordinator;
+
+ @Mock
DreamOverlayContainerView mDreamOverlayContainerView;
@Mock
@@ -109,6 +114,7 @@
mComplicationHostViewController,
mDreamOverlayContentView,
mDreamOverlayStatusBarViewController,
+ mLowLightTransitionCoordinator,
mBlurUtils,
mHandler,
mResources,
@@ -200,7 +206,7 @@
mController.onViewAttached();
- verify(mAnimationsController).startEntryAnimations();
+ verify(mAnimationsController).startEntryAnimations(false);
verify(mAnimationsController, never()).cancelAnimations();
}
@@ -210,11 +216,11 @@
mController.onViewAttached();
- verify(mAnimationsController, never()).startEntryAnimations();
+ verify(mAnimationsController, never()).startEntryAnimations(anyBoolean());
}
@Test
- public void testSkipEntryAnimationsWhenExitingLowLight() {
+ public void testDownwardEntryAnimationsWhenExitingLowLight() {
ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor =
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
when(mStateController.isLowLightActive()).thenReturn(false);
@@ -230,8 +236,14 @@
mController.onViewAttached();
// Entry animations should be started then immediately ended to skip to the end.
- verify(mAnimationsController).startEntryAnimations();
- verify(mAnimationsController).endAnimations();
+ verify(mAnimationsController).startEntryAnimations(true);
+ }
+
+ @Test
+ public void testStartsExitAnimationsBeforeEnteringLowLight() {
+ mController.onBeforeEnterLowLight();
+
+ verify(mAnimationsController).startExitAnimations();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
index dcd8736..068852d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
@@ -22,6 +22,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.view.View;
@@ -33,6 +35,8 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
@@ -96,6 +100,10 @@
private ComplicationHostViewController mController;
+ private SecureSettings mSecureSettings;
+
+ private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -108,12 +116,17 @@
when(mViewHolder.getLayoutParams()).thenReturn(mComplicationLayoutParams);
when(mComplicationView.getParent()).thenReturn(mComplicationHostView);
+ mSecureSettings = new FakeSettings();
+ mSecureSettings.putFloatForUser(
+ Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f, CURRENT_USER_ID);
+
mController = new ComplicationHostViewController(
mComplicationHostView,
mLayoutEngine,
mDreamOverlayStateController,
mLifecycleOwner,
- mViewModel);
+ mViewModel,
+ mSecureSettings);
mController.init();
}
@@ -188,6 +201,23 @@
verify(mComplicationView, never()).setVisibility(View.INVISIBLE);
}
+ @Test
+ public void testAnimationsDisabled_ComplicationsNeverSetToInvisible() {
+ //Disable animations
+ mController.mIsAnimationEnabled = false;
+
+ final Observer<Collection<ComplicationViewModel>> observer =
+ captureComplicationViewModelsObserver();
+
+ // Add a complication before entry animations are finished.
+ final HashSet<ComplicationViewModel> complications = new HashSet<>(
+ Collections.singletonList(mComplicationViewModel));
+ observer.onChanged(complications);
+
+ // The complication view should not be set to invisible.
+ verify(mComplicationView, never()).setVisibility(View.INVISIBLE);
+ }
+
private Observer<Collection<ComplicationViewModel>> captureComplicationViewModelsObserver() {
verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
mObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
new file mode 100644
index 0000000..0e14591
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 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.flags
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class ConditionalRestarterTest : SysuiTestCase() {
+ private lateinit var restarter: ConditionalRestarter
+
+ @Mock private lateinit var systemExitRestarter: SystemExitRestarter
+
+ val restartDelayMs = 0L
+ val dispatcher = StandardTestDispatcher()
+ val testScope = TestScope(dispatcher)
+
+ val conditionA = FakeCondition()
+ val conditionB = FakeCondition()
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ restarter =
+ ConditionalRestarter(
+ systemExitRestarter,
+ setOf(conditionA, conditionB),
+ restartDelayMs,
+ testScope,
+ dispatcher
+ )
+ }
+
+ @Test
+ fun restart_ImmediatelySatisfied() =
+ testScope.runTest {
+ conditionA.canRestart = true
+ conditionB.canRestart = true
+ restarter.restartSystemUI("Restart for test")
+ advanceUntilIdle()
+ verify(systemExitRestarter).restartSystemUI(any())
+ }
+
+ @Test
+ fun restart_WaitsForConditionA() =
+ testScope.runTest {
+ conditionA.canRestart = false
+ conditionB.canRestart = true
+
+ restarter.restartSystemUI("Restart for test")
+ advanceUntilIdle()
+ // No restart occurs yet.
+ verify(systemExitRestarter, never()).restartSystemUI(any())
+
+ conditionA.canRestart = true
+ conditionA.retryFn?.invoke()
+ advanceUntilIdle()
+ verify(systemExitRestarter).restartSystemUI(any())
+ }
+
+ @Test
+ fun restart_WaitsForConditionB() =
+ testScope.runTest {
+ conditionA.canRestart = true
+ conditionB.canRestart = false
+
+ restarter.restartSystemUI("Restart for test")
+ advanceUntilIdle()
+ // No restart occurs yet.
+ verify(systemExitRestarter, never()).restartSystemUI(any())
+
+ conditionB.canRestart = true
+ conditionB.retryFn?.invoke()
+ advanceUntilIdle()
+ verify(systemExitRestarter).restartSystemUI(any())
+ }
+
+ @Test
+ fun restart_WaitsForAllConditions() =
+ testScope.runTest {
+ conditionA.canRestart = true
+ conditionB.canRestart = false
+
+ restarter.restartSystemUI("Restart for test")
+ advanceUntilIdle()
+ // No restart occurs yet.
+ verify(systemExitRestarter, never()).restartSystemUI(any())
+
+ // B becomes true, but A is now false
+ conditionA.canRestart = false
+ conditionB.canRestart = true
+ conditionB.retryFn?.invoke()
+ advanceUntilIdle()
+ // No restart occurs yet.
+ verify(systemExitRestarter, never()).restartSystemUI(any())
+
+ conditionA.canRestart = true
+ conditionA.retryFn?.invoke()
+ advanceUntilIdle()
+ verify(systemExitRestarter).restartSystemUI(any())
+ }
+
+ class FakeCondition : ConditionalRestarter.Condition {
+ var retryFn: (() -> Unit)? = null
+ var canRestart = false
+
+ override fun canRestartNow(retryFn: () -> Unit): Boolean {
+ this.retryFn = retryFn
+
+ return canRestart
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
deleted file mode 100644
index 6060afe..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
+++ /dev/null
@@ -1,146 +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.systemui.flags
-
-import android.test.suitebuilder.annotation.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
-import com.android.systemui.statusbar.policy.BatteryController
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-/**
- * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
- */
-@SmallTest
-class FeatureFlagsReleaseRestarterTest : SysuiTestCase() {
- private lateinit var restarter: FeatureFlagsReleaseRestarter
-
- @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
- @Mock private lateinit var batteryController: BatteryController
- @Mock private lateinit var systemExitRestarter: SystemExitRestarter
- private val executor = FakeExecutor(FakeSystemClock())
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- restarter =
- FeatureFlagsReleaseRestarter(
- wakefulnessLifecycle,
- batteryController,
- executor,
- systemExitRestarter
- )
- }
-
- @Test
- fun testRestart_ScheduledWhenReady() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- whenever(batteryController.isPluggedIn).thenReturn(true)
-
- assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI("Restart for test")
- assertThat(executor.numPending()).isEqualTo(1)
- }
-
- @Test
- fun testRestart_RestartsWhenIdle() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- whenever(batteryController.isPluggedIn).thenReturn(true)
-
- restarter.restartSystemUI("Restart for test")
- verify(systemExitRestarter, never()).restartSystemUI("Restart for test")
- executor.advanceClockToLast()
- executor.runAllReady()
- verify(systemExitRestarter).restartSystemUI(any())
- }
-
- @Test
- fun testRestart_NotScheduledWhenAwake() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
- whenever(batteryController.isPluggedIn).thenReturn(true)
-
- assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI("Restart for test")
- assertThat(executor.numPending()).isEqualTo(0)
- }
-
- @Test
- fun testRestart_NotScheduledWhenNotPluggedIn() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- whenever(batteryController.isPluggedIn).thenReturn(false)
-
- assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI("Restart for test")
- assertThat(executor.numPending()).isEqualTo(0)
- }
-
- @Test
- fun testRestart_NotDoubleSheduled() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- whenever(batteryController.isPluggedIn).thenReturn(true)
-
- assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI("Restart for test")
- restarter.restartSystemUI("Restart for test")
- assertThat(executor.numPending()).isEqualTo(1)
- }
-
- @Test
- fun testWakefulnessLifecycle_CanRestart() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
- whenever(batteryController.isPluggedIn).thenReturn(true)
- assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI("Restart for test")
-
- val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
- verify(wakefulnessLifecycle).addObserver(captor.capture())
-
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-
- captor.value.onFinishedGoingToSleep()
- assertThat(executor.numPending()).isEqualTo(1)
- }
-
- @Test
- fun testBatteryController_CanRestart() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- whenever(batteryController.isPluggedIn).thenReturn(false)
- assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI("Restart for test")
-
- val captor =
- ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
- verify(batteryController).addCallback(captor.capture())
-
- whenever(batteryController.isPluggedIn).thenReturn(true)
-
- captor.value.onBatteryLevelChanged(0, true, true)
- assertThat(executor.numPending()).isEqualTo(1)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
new file mode 100644
index 0000000..647b05a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 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.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.BatteryController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class PluggedInConditionTest : SysuiTestCase() {
+ private lateinit var condition: PluggedInCondition
+
+ @Mock private lateinit var batteryController: BatteryController
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ condition = PluggedInCondition(batteryController)
+ }
+
+ @Test
+ fun testCondition_unplugged() {
+ whenever(batteryController.isPluggedIn).thenReturn(false)
+
+ assertThat(condition.canRestartNow({})).isFalse()
+ }
+
+ @Test
+ fun testCondition_pluggedIn() {
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ assertThat(condition.canRestartNow({})).isTrue()
+ }
+
+ @Test
+ fun testCondition_invokesRetry() {
+ whenever(batteryController.isPluggedIn).thenReturn(false)
+ var retried = false
+ val retryFn = { retried = true }
+
+ // No restart yet, but we do register a listener now.
+ assertThat(condition.canRestartNow(retryFn)).isFalse()
+ val captor =
+ ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
+ verify(batteryController).addCallback(captor.capture())
+
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ captor.value.onBatteryLevelChanged(0, true, true)
+ assertThat(retried).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
similarity index 66%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
index 686782f..f7a773e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -20,12 +20,11 @@
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
-import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Mock
-import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -34,37 +33,45 @@
* Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
*/
@SmallTest
-class FeatureFlagsDebugRestarterTest : SysuiTestCase() {
- private lateinit var restarter: FeatureFlagsDebugRestarter
+class ScreenIdleConditionTest : SysuiTestCase() {
+ private lateinit var condition: ScreenIdleCondition
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
- @Mock private lateinit var systemExitRestarter: SystemExitRestarter
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- restarter = FeatureFlagsDebugRestarter(wakefulnessLifecycle, systemExitRestarter)
+ condition = ScreenIdleCondition(wakefulnessLifecycle)
}
@Test
- fun testRestart_ImmediateWhenAsleep() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- restarter.restartSystemUI("Restart for test")
- verify(systemExitRestarter).restartSystemUI(any())
- }
-
- @Test
- fun testRestart_WaitsForSceenOff() {
+ fun testCondition_awake() {
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
- restarter.restartSystemUI("Restart for test")
- verify(systemExitRestarter, never()).restartSystemUI(any())
+ assertThat(condition.canRestartNow {}).isFalse()
+ }
+ @Test
+ fun testCondition_asleep() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+
+ assertThat(condition.canRestartNow {}).isTrue()
+ }
+
+ @Test
+ fun testCondition_invokesRetry() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+ var retried = false
+ val retryFn = { retried = true }
+
+ // No restart yet, but we do register a listener now.
+ assertThat(condition.canRestartNow(retryFn)).isFalse()
val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
verify(wakefulnessLifecycle).addObserver(captor.capture())
- captor.value.onFinishedGoingToSleep()
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- verify(systemExitRestarter).restartSystemUI(any())
+ captor.value.onFinishedGoingToSleep()
+ assertThat(retried).isTrue()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index cd579db..503e002 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -284,6 +284,24 @@
}
@Test
+ fun `quickAffordance - hidden when quick settings is visible`() =
+ testScope.runTest {
+ repository.setQuickSettingsVisible(true)
+ quickAccessWallet.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = ICON,
+ )
+ )
+
+ val collectedValue =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ )
+
+ assertThat(collectedValue()).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ }
+
+ @Test
fun `quickAffordance - bottom start affordance hidden while dozing`() =
testScope.runTest {
repository.setDozing(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index fe9098f..fc3a638 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -967,6 +967,92 @@
coroutineContext.cancelChildren()
}
+ @Test
+ fun `OCCLUDED to GONE`() =
+ testScope.runTest {
+ // GIVEN a device on lockscreen
+ keyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ // GIVEN a prior transition has run to OCCLUDED
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN keyguard goes away
+ keyguardRepository.setKeyguardShowing(false)
+ // AND occlusion ends
+ keyguardRepository.setKeyguardOccluded(false)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition to GONE should occur
+ assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
+ assertThat(info.to).isEqualTo(KeyguardState.GONE)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `OCCLUDED to LOCKSCREEN`() =
+ testScope.runTest {
+ // GIVEN a device on lockscreen
+ keyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ // GIVEN a prior transition has run to OCCLUDED
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN occlusion ends
+ keyguardRepository.setKeyguardOccluded(false)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition to LOCKSCREEN should occur
+ assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
+ assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
private fun startingToWake() =
WakefulnessModel(
WakefulnessState.STARTING_TO_WAKE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 2a91799..746f668 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -21,7 +21,9 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -44,21 +46,86 @@
private lateinit var underTest: PrimaryBouncerToGoneTransitionViewModel
private lateinit var repository: FakeKeyguardTransitionRepository
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
repository = FakeKeyguardTransitionRepository()
val interactor = KeyguardTransitionInteractor(repository)
- underTest = PrimaryBouncerToGoneTransitionViewModel(interactor, statusBarStateController)
+ underTest =
+ PrimaryBouncerToGoneTransitionViewModel(
+ interactor,
+ statusBarStateController,
+ primaryBouncerInteractor
+ )
+
+ whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
+ whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
}
@Test
+ fun bouncerAlpha() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val job = underTest.bouncerAlpha.onEach { values.add(it) }.launchIn(this)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.6f))
+
+ assertThat(values.size).isEqualTo(3)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
+
+ job.cancel()
+ }
+
+ @Test
+ fun bouncerAlpha_runDimissFromKeyguard() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val job = underTest.bouncerAlpha.onEach { values.add(it) }.launchIn(this)
+
+ whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.6f))
+
+ assertThat(values.size).isEqualTo(3)
+ values.forEach { assertThat(it).isEqualTo(0f) }
+
+ job.cancel()
+ }
+
+ @Test
+ fun scrimAlpha_runDimissFromKeyguard() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<ScrimAlpha>()
+
+ val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
+
+ whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.6f))
+ repository.sendTransitionStep(step(1f))
+
+ assertThat(values.size).isEqualTo(4)
+ values.forEach { assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f)) }
+
+ job.cancel()
+ }
+
+ @Test
fun scrimBehindAlpha_leaveShadeOpen() =
runTest(UnconfinedTestDispatcher()) {
- val values = mutableListOf<Float>()
+ val values = mutableListOf<ScrimAlpha>()
- val job = underTest.scrimBehindAlpha.onEach { values.add(it) }.launchIn(this)
+ val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true)
@@ -68,7 +135,9 @@
repository.sendTransitionStep(step(1f))
assertThat(values.size).isEqualTo(4)
- values.forEach { assertThat(it).isEqualTo(1f) }
+ values.forEach {
+ assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f, behindAlpha = 1f))
+ }
job.cancel()
}
@@ -76,9 +145,9 @@
@Test
fun scrimBehindAlpha_doNotLeaveShadeOpen() =
runTest(UnconfinedTestDispatcher()) {
- val values = mutableListOf<Float>()
+ val values = mutableListOf<ScrimAlpha>()
- val job = underTest.scrimBehindAlpha.onEach { values.add(it) }.launchIn(this)
+ val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
@@ -88,8 +157,10 @@
repository.sendTransitionStep(step(1f))
assertThat(values.size).isEqualTo(4)
- values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
- assertThat(values[3]).isEqualTo(0f)
+ values.forEach { assertThat(it.notificationsAlpha).isEqualTo(0f) }
+ values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) }
+ values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) }
+ assertThat(values[3].behindAlpha).isEqualTo(0f)
job.cancel()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index ab0669a..d428db7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -2031,7 +2031,7 @@
}
@Test
- fun testRetain_sessionPlayer_destroyedWhileActive_fullyRemoved() {
+ fun testRetain_sessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
addPlaybackStateAction()
@@ -2051,6 +2051,40 @@
}
@Test
+ fun testRetain_sessionPlayer_canResume_destroyedWhileActive_setToResume() {
+ whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ addPlaybackStateAction()
+
+ // When a media control using session actions and that does allow resumption is added,
+ addNotificationAndLoad()
+ val dataResumable = mediaDataCaptor.value.copy(resumeAction = Runnable {})
+ mediaDataManager.onMediaDataLoaded(KEY, null, dataResumable)
+
+ // And then the session is destroyed without timing out first
+ sessionCallbackCaptor.value.invoke(KEY)
+
+ // It is converted to a resume player
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(KEY),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ assertThat(mediaDataCaptor.value.resumption).isTrue()
+ assertThat(mediaDataCaptor.value.active).isFalse()
+ verify(logger)
+ .logActiveConvertedToResume(
+ anyInt(),
+ eq(PACKAGE_NAME),
+ eq(mediaDataCaptor.value.instanceId)
+ )
+ }
+
+ @Test
fun testSessionDestroyed_noNotificationKey_stillRemoved() {
whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
index 4dfa626..9ab7289 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
@@ -313,6 +313,25 @@
}
@Test
+ fun testOnLoadTwice_onlyChecksOnce() {
+ // When data is first loaded,
+ setUpMbsWithValidResolveInfo()
+ resumeListener.onMediaDataLoaded(KEY, null, data)
+
+ // We notify the manager to set a null action
+ verify(mediaDataManager).setResumeAction(KEY, null)
+
+ // If we then get another update from the app before the first check completes
+ assertThat(executor.numPending()).isEqualTo(1)
+ var dataWithCheck = data.copy(hasCheckedForResume = true)
+ resumeListener.onMediaDataLoaded(KEY, null, dataWithCheck)
+
+ // We do not try to start another check
+ assertThat(executor.numPending()).isEqualTo(1)
+ verify(mediaDataManager).setResumeAction(KEY, null)
+ }
+
+ @Test
fun testOnUserUnlock_loadsTracks() {
// Set up mock service to successfully find valid media
val description = MediaDescription.Builder().setTitle(TITLE).build()
@@ -392,7 +411,7 @@
assertThat(result.size).isEqualTo(3)
assertThat(result[2].toLong()).isEqualTo(currentTime)
}
- verify(sharedPrefsEditor, times(1)).apply()
+ verify(sharedPrefsEditor).apply()
}
@Test
@@ -432,8 +451,8 @@
resumeListener.userUnlockReceiver.onReceive(mockContext, intent)
// We add its resume controls
- verify(resumeBrowser, times(1)).findRecentMedia()
- verify(mediaDataManager, times(1))
+ verify(resumeBrowser).findRecentMedia()
+ verify(mediaDataManager)
.addResumptionControls(anyInt(), any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
}
@@ -516,7 +535,7 @@
assertThat(result.size).isEqualTo(3)
assertThat(result[2].toLong()).isEqualTo(currentTime)
}
- verify(sharedPrefsEditor, times(1)).apply()
+ verify(sharedPrefsEditor).apply()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 7f57077..e0ca90e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -18,7 +18,6 @@
import android.app.PendingIntent
import android.content.res.ColorStateList
-import android.content.res.Configuration
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.util.MathUtils.abs
@@ -685,46 +684,6 @@
}
@Test
- fun testOnConfigChanged_playersAreAddedBack() {
- mediaCarouselController.pageIndicator = pageIndicator
-
- listener.value.onMediaDataLoaded(
- "playing local",
- null,
- DATA.copy(
- active = true,
- isPlaying = true,
- playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
- )
- listener.value.onMediaDataLoaded(
- "paused local",
- null,
- DATA.copy(
- active = true,
- isPlaying = false,
- playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
- )
- runAllReady()
-
- val playersSize = MediaPlayerData.players().size
-
- configListener.value.onConfigChanged(Configuration())
- runAllReady()
-
- verify(pageIndicator).tintList =
- ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
- assertEquals(playersSize, MediaPlayerData.players().size)
- assertEquals(
- MediaPlayerData.getMediaPlayerIndex("playing local"),
- mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
- )
- }
-
- @Test
fun testOnUiModeChanged_playersAreAddedBack() {
mediaCarouselController.pageIndicator = pageIndicator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index af91cdb..0fac3db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -16,9 +16,12 @@
package com.android.systemui.media.controls.ui
+import android.content.res.Configuration
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
+import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
@@ -58,6 +61,8 @@
@Mock private lateinit var mediaSubTitleWidgetState: WidgetState
@Mock private lateinit var mediaContainerWidgetState: WidgetState
@Mock private lateinit var mediaFlags: MediaFlags
+ @Mock private lateinit var expandedLayout: ConstraintSet
+ @Mock private lateinit var collapsedLayout: ConstraintSet
val delta = 0.1F
@@ -77,6 +82,19 @@
}
@Test
+ fun testOrientationChanged_layoutsAreLoaded() {
+ mediaViewController.expandedLayout = expandedLayout
+ mediaViewController.collapsedLayout = collapsedLayout
+
+ val newConfig = Configuration()
+ newConfig.orientation = ORIENTATION_LANDSCAPE
+ configurationController.onConfigurationChanged(newConfig)
+
+ verify(expandedLayout).load(context, R.xml.media_session_expanded)
+ verify(collapsedLayout).load(context, R.xml.media_session_collapsed)
+ }
+
+ @Test
fun testObtainViewState_applySquishFraction_toPlayerTransitionViewState_height() {
mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
player.measureState =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 376b7cc..608d809 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -20,12 +20,17 @@
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.PackageManager
+import android.os.UserHandle
import android.os.UserManager
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -57,8 +62,8 @@
@Mock lateinit var keyguardManager: KeyguardManager
@Mock lateinit var userManager: UserManager
@Mock lateinit var eventLogger: NoteTaskEventLogger
- @Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ private val userTracker: UserTracker = FakeUserTracker()
private val noteTaskInfo = NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID)
@@ -81,16 +86,14 @@
private fun createNoteTaskController(
isEnabled: Boolean = true,
bubbles: Bubbles? = this.bubbles,
- keyguardManager: KeyguardManager? = this.keyguardManager,
- userManager: UserManager? = this.userManager,
): NoteTaskController =
NoteTaskController(
context = context,
resolver = resolver,
eventLogger = eventLogger,
optionalBubbles = Optional.ofNullable(bubbles),
- optionalUserManager = Optional.ofNullable(userManager),
- optionalKeyguardManager = Optional.ofNullable(keyguardManager),
+ userManager = userManager,
+ keyguardManager = keyguardManager,
isEnabled = isEnabled,
devicePolicyManager = devicePolicyManager,
userTracker = userTracker,
@@ -225,13 +228,19 @@
)
val intentCaptor = argumentCaptor<Intent>()
- verify(context).startActivity(capture(intentCaptor))
+ val userCaptor = argumentCaptor<UserHandle>()
+ verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
- assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK)
+ .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT)
+ .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT)
assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
+ assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
verify(eventLogger).logNoteTaskOpened(expectedInfo)
verifyZeroInteractions(bubbles)
}
@@ -259,7 +268,7 @@
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
- assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
verifyZeroInteractions(eventLogger)
@@ -283,13 +292,22 @@
)
val intentCaptor = argumentCaptor<Intent>()
- verify(context).startActivity(capture(intentCaptor))
+ val userCaptor = argumentCaptor<UserHandle>()
+ verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+
+ (intentCaptor.value.flags and FLAG_ACTIVITY_NEW_TASK) == FLAG_ACTIVITY_NEW_TASK
+
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
- assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK)
+ .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT)
+ .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT)
assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
+ assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
verify(eventLogger).logNoteTaskOpened(expectedInfo)
verifyZeroInteractions(bubbles)
}
@@ -306,28 +324,6 @@
}
@Test
- fun showNoteTask_keyguardManagerIsNull_shouldDoNothing() {
- createNoteTaskController(keyguardManager = null)
- .showNoteTask(
- entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
- isInMultiWindowMode = false,
- )
-
- verifyZeroInteractions(context, bubbles, eventLogger)
- }
-
- @Test
- fun showNoteTask_userManagerIsNull_shouldDoNothing() {
- createNoteTaskController(userManager = null)
- .showNoteTask(
- entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
- isInMultiWindowMode = false,
- )
-
- verifyZeroInteractions(context, bubbles, eventLogger)
- }
-
- @Test
fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() {
whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(null)
@@ -458,9 +454,9 @@
val intentCaptor = argumentCaptor<Intent>()
verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
intentCaptor.value.let { intent ->
- assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
+ assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
- assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
}
@@ -485,9 +481,9 @@
val intentCaptor = argumentCaptor<Intent>()
verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
intentCaptor.value.let { intent ->
- assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
+ assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
- assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
index 7f64f8a..0c945df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
@@ -22,6 +22,8 @@
import android.test.suitebuilder.annotation.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -44,13 +46,14 @@
@Mock lateinit var packageManager: PackageManager
@Mock lateinit var roleManager: RoleManager
+ private val userTracker: UserTracker = FakeUserTracker()
private lateinit var underTest: NoteTaskInfoResolver
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- underTest = NoteTaskInfoResolver(context, roleManager, packageManager)
+ underTest = NoteTaskInfoResolver(roleManager, packageManager, userTracker)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 89606bf..0ab0e2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -52,6 +52,8 @@
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
@@ -60,6 +62,7 @@
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.FakeDisplayTracker;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -103,6 +106,8 @@
@Mock private QSSquishinessController mSquishinessController;
@Mock private FooterActionsViewModel mFooterActionsViewModel;
@Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
+ @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+ @Mock private FeatureFlags mFeatureFlags;
private View mQsFragmentView;
public QSFragmentTest() {
@@ -148,8 +153,9 @@
}
@Test
- public void transitionToFullShade_setsAlphaUsingShadeInterpolator() {
+ public void transitionToFullShade_smallScreen_alphaAlways1() {
QSFragment fragment = resumeAndGetFragment();
+ setIsSmallScreen();
setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
boolean isTransitioningToFullShade = true;
float transitionProgress = 0.5f;
@@ -158,6 +164,43 @@
fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
squishinessFraction);
+ assertThat(mQsFragmentView.getAlpha()).isEqualTo(1f);
+ }
+
+ @Test
+ public void transitionToFullShade_largeScreen_flagEnabled_alphaLargeScreenShadeInterpolator() {
+ when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(true);
+ QSFragment fragment = resumeAndGetFragment();
+ setIsLargeScreen();
+ setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
+ boolean isTransitioningToFullShade = true;
+ float transitionProgress = 0.5f;
+ float squishinessFraction = 0.5f;
+ when(mLargeScreenShadeInterpolator.getQsAlpha(transitionProgress)).thenReturn(123f);
+
+ fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+ squishinessFraction);
+
+ assertThat(mQsFragmentView.getAlpha())
+ .isEqualTo(123f);
+ }
+
+ @Test
+ public void transitionToFullShade_largeScreen_flagDisabled_alphaStandardInterpolator() {
+ when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(false);
+ QSFragment fragment = resumeAndGetFragment();
+ setIsLargeScreen();
+ setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
+ boolean isTransitioningToFullShade = true;
+ float transitionProgress = 0.5f;
+ float squishinessFraction = 0.5f;
+ when(mLargeScreenShadeInterpolator.getQsAlpha(transitionProgress)).thenReturn(123f);
+
+ fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+ squishinessFraction);
+
assertThat(mQsFragmentView.getAlpha())
.isEqualTo(ShadeInterpolation.getContentAlpha(transitionProgress));
}
@@ -514,7 +557,9 @@
mock(DumpManager.class),
mock(QSLogger.class),
mock(FooterActionsController.class),
- mFooterActionsViewModelFactory);
+ mFooterActionsViewModelFactory,
+ mLargeScreenShadeInterpolator,
+ mFeatureFlags);
}
private void setUpOther() {
@@ -622,4 +667,12 @@
return null;
}).when(view).getLocationOnScreen(any(int[].class));
}
+
+ private void setIsLargeScreen() {
+ getFragment().setIsNotificationPanelFullWidth(false);
+ }
+
+ private void setIsSmallScreen() {
+ getFragment().setIsNotificationPanelFullWidth(true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index d6dfc85..ac106ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -57,6 +57,7 @@
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
@@ -351,4 +352,44 @@
.startPendingIntentDismissingKeyguard(
eq(pi), nullable(), nullable<ActivityLaunchAnimator.Controller>())
}
+
+ @Test
+ fun testActiveTileListensOnceAfterCreated() {
+ `when`(tileServiceManager.isActiveTile).thenReturn(true)
+
+ val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ tile.initialize()
+ tile.postStale()
+ testableLooper.processAllMessages()
+
+ verify(tileServiceManager).setBindRequested(true)
+ verify(tileService).onStartListening()
+ }
+
+ @Test
+ fun testActiveTileDoesntListenAfterFirstTime() {
+ `when`(tileServiceManager.isActiveTile).thenReturn(true)
+
+ val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ tile.initialize()
+ // Make sure we have an icon in the tile because we don't have a default icon
+ // This should not be overridden by the retrieved tile that has null icon.
+ tile.qsTile.icon = mock(Icon::class.java)
+ `when`(tile.qsTile.icon.loadDrawable(any(Context::class.java)))
+ .thenReturn(mock(Drawable::class.java))
+
+ tile.postStale()
+ testableLooper.processAllMessages()
+
+ // postStale will set it to not listening after it's done
+ verify(tileService).onStopListening()
+
+ clearInvocations(tileServiceManager, tileService)
+
+ tile.setListening(Any(), true)
+ testableLooper.processAllMessages()
+
+ verify(tileServiceManager, never()).setBindRequested(true)
+ verify(tileService, never()).onStartListening()
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 4f469f7..2dfb6e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -22,8 +22,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -35,6 +33,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
import android.annotation.IdRes;
import android.content.ContentResolver;
import android.content.res.Configuration;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt
new file mode 100644
index 0000000..8309342
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt
@@ -0,0 +1,144 @@
+package com.android.systemui.shade.transition
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.google.common.truth.Expect
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class LargeScreenShadeInterpolatorImplTest : SysuiTestCase() {
+ @get:Rule val expect: Expect = Expect.create()
+
+ private val portraitShadeInterpolator = LargeScreenPortraitShadeInterpolator()
+ private val splitShadeInterpolator = SplitShadeInterpolator()
+ private val configurationController = FakeConfigurationController()
+ private val impl =
+ LargeScreenShadeInterpolatorImpl(
+ configurationController,
+ context,
+ splitShadeInterpolator,
+ portraitShadeInterpolator
+ )
+
+ @Test
+ fun getBehindScrimAlpha_inSplitShade_usesSplitShadeValue() {
+ setSplitShadeEnabled(true)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getBehindScrimAlpha(fraction) },
+ expected = { fraction -> splitShadeInterpolator.getBehindScrimAlpha(fraction) }
+ )
+ }
+
+ @Test
+ fun getBehindScrimAlpha_inPortraitShade_usesPortraitShadeValue() {
+ setSplitShadeEnabled(false)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getBehindScrimAlpha(fraction) },
+ expected = { fraction -> portraitShadeInterpolator.getBehindScrimAlpha(fraction) }
+ )
+ }
+
+ @Test
+ fun getNotificationScrimAlpha_inSplitShade_usesSplitShadeValue() {
+ setSplitShadeEnabled(true)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getNotificationScrimAlpha(fraction) },
+ expected = { fraction -> splitShadeInterpolator.getNotificationScrimAlpha(fraction) }
+ )
+ }
+ @Test
+ fun getNotificationScrimAlpha_inPortraitShade_usesPortraitShadeValue() {
+ setSplitShadeEnabled(false)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getNotificationScrimAlpha(fraction) },
+ expected = { fraction -> portraitShadeInterpolator.getNotificationScrimAlpha(fraction) }
+ )
+ }
+
+ @Test
+ fun getNotificationContentAlpha_inSplitShade_usesSplitShadeValue() {
+ setSplitShadeEnabled(true)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getNotificationContentAlpha(fraction) },
+ expected = { fraction -> splitShadeInterpolator.getNotificationContentAlpha(fraction) }
+ )
+ }
+
+ @Test
+ fun getNotificationContentAlpha_inPortraitShade_usesPortraitShadeValue() {
+ setSplitShadeEnabled(false)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getNotificationContentAlpha(fraction) },
+ expected = { fraction ->
+ portraitShadeInterpolator.getNotificationContentAlpha(fraction)
+ }
+ )
+ }
+
+ @Test
+ fun getNotificationFooterAlpha_inSplitShade_usesSplitShadeValue() {
+ setSplitShadeEnabled(true)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getNotificationFooterAlpha(fraction) },
+ expected = { fraction -> splitShadeInterpolator.getNotificationFooterAlpha(fraction) }
+ )
+ }
+ @Test
+ fun getNotificationFooterAlpha_inPortraitShade_usesPortraitShadeValue() {
+ setSplitShadeEnabled(false)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getNotificationFooterAlpha(fraction) },
+ expected = { fraction ->
+ portraitShadeInterpolator.getNotificationFooterAlpha(fraction)
+ }
+ )
+ }
+
+ @Test
+ fun getQsAlpha_inSplitShade_usesSplitShadeValue() {
+ setSplitShadeEnabled(true)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getQsAlpha(fraction) },
+ expected = { fraction -> splitShadeInterpolator.getQsAlpha(fraction) }
+ )
+ }
+ @Test
+ fun getQsAlpha_inPortraitShade_usesPortraitShadeValue() {
+ setSplitShadeEnabled(false)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getQsAlpha(fraction) },
+ expected = { fraction -> portraitShadeInterpolator.getQsAlpha(fraction) }
+ )
+ }
+
+ private fun setSplitShadeEnabled(enabled: Boolean) {
+ overrideResource(R.bool.config_use_split_notification_shade, enabled)
+ configurationController.notifyConfigurationChanged()
+ }
+
+ private fun assertInterpolation(
+ actual: (fraction: Float) -> Float,
+ expected: (fraction: Float) -> Float
+ ) {
+ for (i in 0..10) {
+ val fraction = i / 10f
+ expect.that(actual(fraction)).isEqualTo(expected(fraction))
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt
new file mode 100644
index 0000000..d24bcdc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 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.shade.transition
+
+class LinearLargeScreenShadeInterpolator : LargeScreenShadeInterpolator {
+ override fun getBehindScrimAlpha(fraction: Float) = fraction
+ override fun getNotificationScrimAlpha(fraction: Float) = fraction
+ override fun getNotificationContentAlpha(fraction: Float) = fraction
+ override fun getNotificationFooterAlpha(fraction: Float) = fraction
+ override fun getQsAlpha(fraction: Float) = fraction
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
index 84f8656..cbf5485 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
@@ -5,6 +5,8 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.shade.STATE_CLOSED
import com.android.systemui.shade.STATE_OPEN
import com.android.systemui.shade.STATE_OPENING
@@ -30,6 +32,7 @@
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
@Mock private lateinit var headsUpManager: HeadsUpManager
+ @Mock private lateinit var featureFlags: FeatureFlags
private val configurationController = FakeConfigurationController()
private lateinit var controller: ScrimShadeTransitionController
@@ -45,7 +48,8 @@
scrimController,
context.resources,
statusBarStateController,
- headsUpManager)
+ headsUpManager,
+ featureFlags)
controller.onPanelStateChanged(STATE_OPENING)
}
@@ -107,6 +111,19 @@
}
@Test
+ fun onPanelExpansionChanged_inSplitShade_flagTrue_setsFractionEqualToEventFraction() {
+ whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(true)
+ whenever(statusBarStateController.currentOrUpcomingState)
+ .thenReturn(StatusBarState.SHADE)
+ setSplitShadeEnabled(true)
+
+ controller.onPanelExpansionChanged(EXPANSION_EVENT)
+
+ verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
+ }
+
+ @Test
fun onPanelExpansionChanged_inSplitShade_onKeyguard_setsFractionEqualToEventFraction() {
whenever(statusBarStateController.currentOrUpcomingState)
.thenReturn(StatusBarState.KEYGUARD)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
index cc45cf88..2eca78a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
@@ -57,7 +57,18 @@
clockView.measure(50, 50)
verify(mockTextAnimator).glyphFilter = any()
- verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, false, 350L, null, 0L, null)
+ verify(mockTextAnimator)
+ .setTextStyle(
+ weight = 300,
+ textSize = -1.0f,
+ color = 200,
+ strokeWidth = -1F,
+ animate = false,
+ duration = 350L,
+ interpolator = null,
+ delay = 0L,
+ onAnimationEnd = null
+ )
verifyNoMoreInteractions(mockTextAnimator)
}
@@ -68,8 +79,30 @@
clockView.animateAppearOnLockscreen()
verify(mockTextAnimator, times(2)).glyphFilter = any()
- verify(mockTextAnimator).setTextStyle(100, -1.0f, 200, false, 0L, null, 0L, null)
- verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, true, 350L, null, 0L, null)
+ verify(mockTextAnimator)
+ .setTextStyle(
+ weight = 100,
+ textSize = -1.0f,
+ color = 200,
+ strokeWidth = -1F,
+ animate = false,
+ duration = 0L,
+ interpolator = null,
+ delay = 0L,
+ onAnimationEnd = null
+ )
+ verify(mockTextAnimator)
+ .setTextStyle(
+ weight = 300,
+ textSize = -1.0f,
+ color = 200,
+ strokeWidth = -1F,
+ animate = true,
+ duration = 350L,
+ interpolator = null,
+ delay = 0L,
+ onAnimationEnd = null
+ )
verifyNoMoreInteractions(mockTextAnimator)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt
new file mode 100644
index 0000000..eac0e29
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2023 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.collection.coordinator
+
+import android.app.Notification
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.SbnBuilder
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener
+import com.android.systemui.statusbar.notification.collection.render.NotifGroupController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.SystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.InjectMocks
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class GroupWhenCoordinatorTest : SysuiTestCase() {
+
+ private lateinit var beforeFinalizeFilterListener: OnBeforeFinalizeFilterListener
+ private lateinit var afterRenderGroupListener: OnAfterRenderGroupListener
+
+ @Mock private lateinit var pipeline: NotifPipeline
+
+ @Mock private lateinit var delayableExecutor: DelayableExecutor
+
+ @Mock private lateinit var groupController: NotifGroupController
+
+ @Mock private lateinit var systemClock: SystemClock
+
+ @InjectMocks private lateinit var coordinator: GroupWhenCoordinator
+
+ @Before
+ fun setUp() {
+ initMocks(this)
+ whenever(systemClock.currentTimeMillis()).thenReturn(NOW)
+ coordinator.attach(pipeline)
+
+ beforeFinalizeFilterListener = withArgCaptor {
+ verify(pipeline).addOnBeforeFinalizeFilterListener(capture())
+ }
+ afterRenderGroupListener = withArgCaptor {
+ verify(pipeline).addOnAfterRenderGroupListener(capture())
+ }
+ }
+
+ @Test
+ fun setNotificationGroupWhen_setClosestTimeByNow_whenAllNotificationsAreBeforeNow() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, NOW - 10L)
+ val childEntry2 = buildNotificationEntry(2, NOW - 100L)
+ val groupEntry =
+ GroupEntryBuilder()
+ .setSummary(summaryEntry)
+ .setChildren(listOf(childEntry1, childEntry2))
+ .build()
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ // THEN
+ verify(groupController).setNotificationGroupWhen(eq(NOW - 10L))
+ }
+
+ @Test
+ fun setNotificationGroupWhen_setClosestTimeByNow_whenAllNotificationsAreAfterNow() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, NOW + 10L)
+ val childEntry2 = buildNotificationEntry(2, NOW + 100L)
+
+ val groupEntry =
+ GroupEntryBuilder()
+ .setSummary(summaryEntry)
+ .setChildren(listOf(childEntry1, childEntry2))
+ .build()
+
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ // THEN
+ verify(groupController).setNotificationGroupWhen(eq(NOW + 10L))
+ }
+
+ @Test
+ fun setNotificationGroupWhen_setClosestFutureTimeByNow_whenThereAreBothBeforeAndAfterNow() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, NOW + 100L)
+ val childEntry2 = buildNotificationEntry(2, NOW + 10L)
+ val childEntry3 = buildNotificationEntry(3, NOW - 100L)
+ val childEntry4 = buildNotificationEntry(4, NOW - 9L)
+
+ val groupEntry =
+ GroupEntryBuilder()
+ .setSummary(summaryEntry)
+ .setChildren(listOf(childEntry1, childEntry2, childEntry3, childEntry4))
+ .build()
+
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ // THEN
+ verify(groupController).setNotificationGroupWhen(eq(NOW + 10L))
+ }
+
+ @Test
+ fun setNotificationGroupWhen_filterInvalidNotificationTimes() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, NOW + 100L)
+ val childEntry2 = buildNotificationEntry(2, -20000L)
+ val childEntry3 = buildNotificationEntry(4, 0)
+
+ val groupEntry =
+ GroupEntryBuilder()
+ .setSummary(summaryEntry)
+ .setChildren(listOf(childEntry1, childEntry2, childEntry3))
+ .build()
+
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ // THEN
+ verify(groupController).setNotificationGroupWhen(eq(NOW + 100))
+ }
+
+ @Test
+ fun setNotificationGroupWhen_setSummaryTimeWhenAllNotificationTimesAreInvalid() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, 0)
+ val childEntry2 = buildNotificationEntry(2, -1)
+
+ val groupEntry =
+ GroupEntryBuilder()
+ .setSummary(summaryEntry)
+ .setChildren(listOf(childEntry1, childEntry2))
+ .build()
+
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ // THEN
+ verify(groupController, never()).setNotificationGroupWhen(NOW)
+ }
+
+ @Test
+ fun setNotificationGroupWhen_schedulePipelineInvalidationWhenAnyNotificationIsInTheFuture() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, NOW + 1000L)
+ val childEntry2 = buildNotificationEntry(2, NOW + 2000L)
+ val childEntry3 = buildNotificationEntry(3, NOW - 100L)
+
+ val groupEntry =
+ GroupEntryBuilder()
+ .setSummary(summaryEntry)
+ .setChildren(listOf(childEntry1, childEntry2, childEntry3))
+ .build()
+
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ // THEN
+ verify(delayableExecutor).executeDelayed(any(), eq(1000))
+ }
+
+ @Test
+ fun setNotificationGroupWhen_cancelPrevPipelineInvalidation() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, NOW + 1L)
+ val prevInvalidation = mock<Runnable>()
+ whenever(delayableExecutor.executeDelayed(any(), any())).thenReturn(prevInvalidation)
+
+ val groupEntry =
+ GroupEntryBuilder().setSummary(summaryEntry).setChildren(listOf(childEntry1)).build()
+
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+
+ // THEN
+ verify(prevInvalidation).run()
+ }
+
+ private fun buildNotificationEntry(id: Int, timeMillis: Long): NotificationEntry {
+ val notification = Notification.Builder(mContext).setWhen(timeMillis).build()
+ val sbn = SbnBuilder().setNotification(notification).build()
+ return NotificationEntryBuilder().setId(id).setSbn(sbn).build()
+ }
+
+ private companion object {
+ private const val NOW = 1000L
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 110926c..1cd182b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -100,6 +100,7 @@
FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true);
+ fakeFeatureFlags.set(Flags.SENSITIVE_REVEAL_ANIM, false);
mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags);
}
@@ -399,17 +400,6 @@
}
@Test
- public void testIsBlockingHelperShowing_isCorrectlyUpdated() throws Exception {
- ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
-
- group.setBlockingHelperShowing(true);
- assertTrue(group.isBlockingHelperShowing());
-
- group.setBlockingHelperShowing(false);
- assertFalse(group.isBlockingHelperShowing());
- }
-
- @Test
public void testGetNumUniqueChildren_defaultChannel() throws Exception {
ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index d7ac6b4..3d8a744 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -117,7 +117,6 @@
@Mock private NotificationPresenter mPresenter;
@Mock private NotificationActivityStarter mNotificationActivityStarter;
@Mock private NotificationListContainer mNotificationListContainer;
- @Mock private NotificationInfo.CheckSaveListener mCheckSaveListener;
@Mock private OnSettingsClickListener mOnSettingsClickListener;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private CentralSurfaces mCentralSurfaces;
@@ -173,7 +172,6 @@
// Test doesn't support animation since the guts view is not attached.
doNothing().when(guts).openControls(
- eq(true) /* shouldDoCircularReveal */,
anyInt(),
anyInt(),
anyBoolean(),
@@ -190,7 +188,6 @@
assertEquals(View.INVISIBLE, guts.getVisibility());
mTestableLooper.processAllMessages();
verify(guts).openControls(
- eq(true),
anyInt(),
anyInt(),
anyBoolean(),
@@ -213,7 +210,6 @@
// Test doesn't support animation since the guts view is not attached.
doNothing().when(guts).openControls(
- eq(true) /* shouldDoCircularReveal */,
anyInt(),
anyInt(),
anyBoolean(),
@@ -237,7 +233,6 @@
assertTrue(mGutsManager.openGutsInternal(row, 0, 0, menuItem));
mTestableLooper.processAllMessages();
verify(guts).openControls(
- eq(true),
anyInt(),
anyInt(),
anyBoolean(),
@@ -379,7 +374,6 @@
public void testInitializeNotificationInfoView_PassesAlongProvisionedState() throws Exception {
NotificationInfo notificationInfoView = mock(NotificationInfo.class);
ExpandableNotificationRow row = spy(mHelper.createRow());
- row.setBlockingHelperShowing(false);
modifyRanking(row.getEntry())
.setUserSentiment(USER_SENTIMENT_NEGATIVE)
.build();
@@ -414,7 +408,6 @@
public void testInitializeNotificationInfoView_withInitialAction() throws Exception {
NotificationInfo notificationInfoView = mock(NotificationInfo.class);
ExpandableNotificationRow row = spy(mHelper.createRow());
- row.setBlockingHelperShowing(true);
modifyRanking(row.getEntry())
.setUserSentiment(USER_SENTIMENT_NEGATIVE)
.build();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
index e696c87..fdfb4f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
@@ -76,7 +76,7 @@
fun openControls() {
guts.gutsContent = gutsContent
- guts.openControls(true, 0, 0, false, null)
+ guts.openControls(0, 0, false, null)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
index 87f4c32..09382ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
@@ -20,6 +20,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.util.mockito.mock
@@ -39,6 +41,8 @@
private val sectionProvider = StackScrollAlgorithm.SectionProvider { _, _ -> false }
private val bypassController = StackScrollAlgorithm.BypassController { false }
private val statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
+ private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>()
+ private val featureFlags = mock<FeatureFlags>()
private lateinit var sut: AmbientState
@@ -51,6 +55,8 @@
sectionProvider,
bypassController,
statusBarKeyguardViewManager,
+ largeScreenShadeInterpolator,
+ featureFlags
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 9d759c4..b1d3daa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -7,6 +7,9 @@
import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ShadeInterpolation
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.LegacySourceType
@@ -21,7 +24,9 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
/**
@@ -32,6 +37,9 @@
@RunWithLooper
class NotificationShelfTest : SysuiTestCase() {
+ @Mock private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator
+ @Mock private lateinit var flags: FeatureFlags
+
private val shelf = NotificationShelf(
context,
/* attrs */ null,
@@ -50,8 +58,12 @@
@Before
fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(ambientState.largeScreenShadeInterpolator).thenReturn(largeScreenShadeInterpolator)
+ whenever(ambientState.featureFlags).thenReturn(flags)
shelf.bind(ambientState, /* hostLayoutController */ hostLayoutController)
shelf.layout(/* left */ 0, /* top */ 0, /* right */ 30, /* bottom */5)
+ whenever(ambientState.isSmallScreen).thenReturn(true)
}
@Test
@@ -295,7 +307,35 @@
fun updateState_expansionChanging_shelfAlphaUpdated() {
updateState_expansionChanging_shelfAlphaUpdated(
expansionFraction = 0.6f,
- expectedAlpha = ShadeInterpolation.getContentAlpha(0.6f)
+ expectedAlpha = ShadeInterpolation.getContentAlpha(0.6f),
+ )
+ }
+
+ @Test
+ fun updateState_flagTrue_largeScreen_expansionChanging_shelfAlphaUpdated_largeScreenValue() {
+ val expansionFraction = 0.6f
+ whenever(flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)).thenReturn(true)
+ whenever(ambientState.isSmallScreen).thenReturn(false)
+ whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
+ .thenReturn(0.123f)
+
+ updateState_expansionChanging_shelfAlphaUpdated(
+ expansionFraction = expansionFraction,
+ expectedAlpha = 0.123f
+ )
+ }
+
+ @Test
+ fun updateState_flagFalse_largeScreen_expansionChanging_shelfAlphaUpdated_standardValue() {
+ val expansionFraction = 0.6f
+ whenever(flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)).thenReturn(false)
+ whenever(ambientState.isSmallScreen).thenReturn(false)
+ whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
+ .thenReturn(0.123f)
+
+ updateState_expansionChanging_shelfAlphaUpdated(
+ expansionFraction = expansionFraction,
+ expectedAlpha = ShadeInterpolation.getContentAlpha(expansionFraction)
)
}
@@ -305,7 +345,17 @@
updateState_expansionChanging_shelfAlphaUpdated(
expansionFraction = 0.95f,
- expectedAlpha = aboutToShowBouncerProgress(0.95f)
+ expectedAlpha = aboutToShowBouncerProgress(0.95f),
+ )
+ }
+
+ @Test
+ fun updateState_largeScreen_expansionChangingWhileBouncerInTransit_bouncerInterpolatorUsed() {
+ whenever(ambientState.isBouncerInTransit).thenReturn(true)
+
+ updateState_expansionChanging_shelfAlphaUpdated(
+ expansionFraction = 0.95f,
+ expectedAlpha = aboutToShowBouncerProgress(0.95f),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 89c399b..485f2be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -52,7 +52,6 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -136,7 +135,6 @@
@Mock private StackStateLogger mStackLogger;
@Mock private NotificationStackScrollLogger mLogger;
@Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
- @Mock private ShadeTransitionController mShadeTransitionController;
@Mock private FeatureFlags mFeatureFlags;
@Mock private NotificationTargetsHelper mNotificationTargetsHelper;
@Mock private SecureSettings mSecureSettings;
@@ -184,7 +182,6 @@
mNotifPipelineFlags,
mNotifCollection,
mLockscreenShadeTransitionController,
- mShadeTransitionController,
mUiEventLogger,
mRemoteInputManager,
mVisibilityLocationProviderDelegator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index dd7143a..7153e59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.stack;
import static android.view.View.GONE;
+import static android.view.WindowInsets.Type.ime;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
@@ -46,6 +47,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -54,6 +56,8 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
import android.widget.TextView;
import androidx.test.annotation.UiThreadTest;
@@ -64,7 +68,9 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationShelfController;
@@ -91,6 +97,8 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.ArrayList;
+
/**
* Tests for {@link NotificationStackScrollLayout}.
*/
@@ -123,6 +131,8 @@
@Mock private NotificationShelf mNotificationShelf;
@Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+ @Mock private FeatureFlags mFeatureFlags;
@Before
@UiThreadTest
@@ -136,7 +146,10 @@
mDumpManager,
mNotificationSectionsManager,
mBypassController,
- mStatusBarKeyguardViewManager));
+ mStatusBarKeyguardViewManager,
+ mLargeScreenShadeInterpolator,
+ mFeatureFlags
+ ));
// Inject dependencies before initializing the layout
mDependency.injectTestDependency(SysuiStatusBarStateController.class, mBarState);
@@ -843,6 +856,19 @@
verify(mEmptyShadeView).setFooterText(not(0));
}
+ @Test
+ public void testWindowInsetAnimationProgress_updatesBottomInset() {
+ int bottomImeInset = 100;
+ mStackScrollerInternal.setAnimatedInsetsEnabled(true);
+ WindowInsets windowInsets = new WindowInsets.Builder()
+ .setInsets(ime(), Insets.of(0, 0, 0, bottomImeInset)).build();
+ ArrayList<WindowInsetsAnimation> windowInsetsAnimations = new ArrayList<>();
+ mStackScrollerInternal
+ .dispatchWindowInsetsAnimationProgress(windowInsets, windowInsetsAnimations);
+
+ assertEquals(bottomImeInset, mStackScrollerInternal.mBottomInset);
+ }
+
private void setBarStateForTest(int state) {
// Can't inject this through the listener or we end up on the actual implementation
// rather than the mock because the spy just coppied the anonymous inner /shruggie.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 4d9db8c..7f20f1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -8,6 +8,9 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ShadeInterpolation.getContentAlpha
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.EmptyShadeView
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarState
@@ -15,11 +18,13 @@
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito.any
import org.mockito.Mockito.eq
@@ -30,12 +35,19 @@
@SmallTest
class StackScrollAlgorithmTest : SysuiTestCase() {
+
+ @JvmField @Rule
+ var expect: Expect = Expect.create()
+
+ private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>()
+
private val hostView = FrameLayout(context)
private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView)
private val notificationRow = mock<ExpandableNotificationRow>()
private val dumpManager = mock<DumpManager>()
private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
private val notificationShelf = mock<NotificationShelf>()
+ private val featureFlags = mock<FeatureFlags>()
private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply {
layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
}
@@ -44,8 +56,10 @@
dumpManager,
/* sectionProvider */ { _, _ -> false },
/* bypassController */ { false },
- mStatusBarKeyguardViewManager
- )
+ mStatusBarKeyguardViewManager,
+ largeScreenShadeInterpolator,
+ featureFlags,
+ )
private val testableResources = mContext.getOrCreateTestableResources()
@@ -59,6 +73,7 @@
fun setUp() {
whenever(notificationShelf.viewState).thenReturn(ExpandableViewState())
whenever(notificationRow.viewState).thenReturn(ExpandableViewState())
+ ambientState.isSmallScreen = true
hostView.addView(notificationRow)
}
@@ -145,11 +160,46 @@
}
@Test
- fun resetViewStates_expansionChangingWhileBouncerInTransit_notificationAlphaUpdated() {
+ fun resetViewStates_flagTrue_largeScreen_expansionChanging_alphaUpdated_largeScreenValue() {
+ val expansionFraction = 0.6f
+ val surfaceAlpha = 123f
+ ambientState.isSmallScreen = false
+ whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(true)
+ whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
+ whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
+ .thenReturn(surfaceAlpha)
+
+ resetViewStates_expansionChanging_notificationAlphaUpdated(
+ expansionFraction = expansionFraction,
+ expectedAlpha = surfaceAlpha,
+ )
+ }
+
+ @Test
+ fun resetViewStates_flagFalse_largeScreen_expansionChanging_alphaUpdated_standardValue() {
+ val expansionFraction = 0.6f
+ val surfaceAlpha = 123f
+ ambientState.isSmallScreen = false
+ whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(false)
+ whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
+ whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
+ .thenReturn(surfaceAlpha)
+
+ resetViewStates_expansionChanging_notificationAlphaUpdated(
+ expansionFraction = expansionFraction,
+ expectedAlpha = getContentAlpha(expansionFraction),
+ )
+ }
+
+ @Test
+ fun expansionChanging_largeScreen_bouncerInTransit_alphaUpdated_bouncerValues() {
+ ambientState.isSmallScreen = false
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
resetViewStates_expansionChanging_notificationAlphaUpdated(
expansionFraction = 0.95f,
- expectedAlpha = aboutToShowBouncerProgress(0.95f)
+ expectedAlpha = aboutToShowBouncerProgress(0.95f),
)
}
@@ -696,7 +746,7 @@
private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
expansionFraction: Float,
- expectedAlpha: Float
+ expectedAlpha: Float,
) {
ambientState.isExpansionChanging = true
ambientState.expansionFraction = expansionFraction
@@ -704,7 +754,7 @@
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
- assertThat(notificationRow.viewState.alpha).isEqualTo(expectedAlpha)
+ expect.that(notificationRow.viewState.alpha).isEqualTo(expectedAlpha)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index e1fba81..7a1270f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -24,8 +24,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -41,6 +39,8 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
import android.animation.Animator;
import android.app.AlarmManager;
import android.graphics.Color;
@@ -59,6 +59,8 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
@@ -67,7 +69,8 @@
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.scrim.ScrimView;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
+import com.android.systemui.shade.transition.LinearLargeScreenShadeInterpolator;
import com.android.systemui.statusbar.policy.FakeConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -104,6 +107,8 @@
private final FakeConfigurationController mConfigurationController =
new FakeConfigurationController();
+ private final LargeScreenShadeInterpolator
+ mLinearLargeScreenShadeInterpolator = new LinearLargeScreenShadeInterpolator();
private ScrimController mScrimController;
private ScrimView mScrimBehind;
@@ -128,11 +133,11 @@
@Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
@Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Mock private CoroutineDispatcher mMainDispatcher;
- @Mock private SysuiStatusBarStateController mSysuiStatusBarStateController;
// TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
// event-dispatch-on-registration pattern caused some of these unit tests to fail.)
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ @Mock private FeatureFlags mFeatureFlags;
private static class AnimatorListener implements Animator.AnimatorListener {
private int mNumStarts;
@@ -242,20 +247,28 @@
when(mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition())
.thenReturn(emptyFlow());
- when(mPrimaryBouncerToGoneTransitionViewModel.getScrimBehindAlpha())
+ when(mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha())
.thenReturn(emptyFlow());
- mScrimController = new ScrimController(mLightBarController,
- mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder,
- new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor,
- mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
+ mScrimController = new ScrimController(
+ mLightBarController,
+ mDozeParameters,
+ mAlarmManager,
+ mKeyguardStateController,
+ mDelayedWakeLockBuilder,
+ new FakeHandler(mLooper.getLooper()),
+ mKeyguardUpdateMonitor,
+ mDockManager,
+ mConfigurationController,
+ new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
mStatusBarKeyguardViewManager,
mPrimaryBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
- mSysuiStatusBarStateController,
- mMainDispatcher);
+ mMainDispatcher,
+ mLinearLargeScreenShadeInterpolator,
+ mFeatureFlags);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -653,7 +666,81 @@
}
@Test
- public void transitionToUnlocked() {
+ public void transitionToUnlocked_clippedQs() {
+ mScrimController.setClipsQsScrim(true);
+ mScrimController.setRawPanelExpansionFraction(0f);
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ finishAnimationsImmediately();
+
+ assertScrimTinted(Map.of(
+ mNotificationsScrim, false,
+ mScrimInFront, false,
+ mScrimBehind, true
+ ));
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, TRANSPARENT,
+ mScrimBehind, OPAQUE));
+
+ mScrimController.setRawPanelExpansionFraction(0.25f);
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, SEMI_TRANSPARENT,
+ mScrimBehind, OPAQUE));
+
+ mScrimController.setRawPanelExpansionFraction(0.5f);
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, OPAQUE,
+ mScrimBehind, OPAQUE));
+ }
+
+ @Test
+ public void transitionToUnlocked_nonClippedQs_flagTrue_followsLargeScreensInterpolator() {
+ when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(true);
+ mScrimController.setClipsQsScrim(false);
+ mScrimController.setRawPanelExpansionFraction(0f);
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ finishAnimationsImmediately();
+
+ assertScrimTinted(Map.of(
+ mNotificationsScrim, false,
+ mScrimInFront, false,
+ mScrimBehind, true
+ ));
+ // The large screens interpolator used in this test is a linear one, just for tests.
+ // Assertions below are based on this assumption, and that the code uses that interpolator
+ // when on a large screen (QS not clipped).
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, TRANSPARENT,
+ mScrimBehind, TRANSPARENT));
+
+ mScrimController.setRawPanelExpansionFraction(0.5f);
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, SEMI_TRANSPARENT,
+ mScrimBehind, SEMI_TRANSPARENT));
+
+ mScrimController.setRawPanelExpansionFraction(0.99f);
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, SEMI_TRANSPARENT,
+ mScrimBehind, SEMI_TRANSPARENT));
+
+ mScrimController.setRawPanelExpansionFraction(1f);
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, OPAQUE,
+ mScrimBehind, OPAQUE));
+ }
+
+
+ @Test
+ public void transitionToUnlocked_nonClippedQs_flagFalse() {
+ when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(false);
mScrimController.setClipsQsScrim(false);
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -691,7 +778,6 @@
mScrimBehind, OPAQUE));
}
-
@Test
public void scrimStateCallback() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -879,17 +965,25 @@
// GIVEN display does NOT need blanking
when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
- mScrimController = new ScrimController(mLightBarController,
- mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder,
- new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor,
- mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
+ mScrimController = new ScrimController(
+ mLightBarController,
+ mDozeParameters,
+ mAlarmManager,
+ mKeyguardStateController,
+ mDelayedWakeLockBuilder,
+ new FakeHandler(mLooper.getLooper()),
+ mKeyguardUpdateMonitor,
+ mDockManager,
+ mConfigurationController,
+ new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
mStatusBarKeyguardViewManager,
mPrimaryBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
- mSysuiStatusBarStateController,
- mMainDispatcher);
+ mMainDispatcher,
+ mLinearLargeScreenShadeInterpolator,
+ mFeatureFlags);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
index 1779de7..7594c90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
@@ -8,13 +8,14 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.any
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -26,6 +27,9 @@
@Mock
private lateinit var display: Display
+ @Mock
+ private lateinit var currentActivityTypeProvider: CurrentActivityTypeProvider
+
private val view: View = View(context)
private val progressProvider = TestUnfoldTransitionProvider()
private val scopedProvider = ScopedUnfoldTransitionProgressProvider(progressProvider)
@@ -36,9 +40,9 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(windowManager.defaultDisplay).thenReturn(display)
- `when`(display.rotation).thenReturn(Surface.ROTATION_0)
- `when`(display.getSize(any())).thenAnswer {
+ whenever(windowManager.defaultDisplay).thenReturn(display)
+ whenever(display.rotation).thenReturn(Surface.ROTATION_0)
+ whenever(display.getSize(any())).thenAnswer {
val point = it.arguments[0] as Point
point.x = 100
point.y = 100
@@ -47,7 +51,12 @@
scopedProvider.setReadyToHandleTransition(true)
- controller = StatusBarMoveFromCenterAnimationController(scopedProvider, windowManager)
+ controller =
+ StatusBarMoveFromCenterAnimationController(
+ scopedProvider,
+ currentActivityTypeProvider,
+ windowManager
+ )
}
@Test
@@ -99,6 +108,31 @@
}
@Test
+ fun alpha_onLauncher_alphaDoesNotChange() {
+ whenever(currentActivityTypeProvider.isHomeActivity).thenReturn(true)
+ controller.onViewsReady(arrayOf(view))
+ progressProvider.onTransitionStarted()
+ progressProvider.onTransitionProgress(0.0f)
+ assertThat(view.alpha).isEqualTo(1.0f)
+
+ progressProvider.onTransitionProgress(1.0f)
+
+ assertThat(view.alpha).isEqualTo(1.0f)
+ }
+
+ @Test
+ fun alpha_NotOnLauncher_alphaChanges() {
+ whenever(currentActivityTypeProvider.isHomeActivity).thenReturn(false)
+ controller.onViewsReady(arrayOf(view))
+ progressProvider.onTransitionStarted()
+ assertThat(view.alpha).isEqualTo(1.0f)
+
+ progressProvider.onTransitionProgress(0.5f)
+
+ assertThat(view.alpha).isNotEqualTo(1.0f)
+ }
+
+ @Test
fun transitionFinished_viewReAttached_noChangesToTranslation() {
controller.onViewsReady(arrayOf(view))
progressProvider.onTransitionProgress(0.5f)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt
similarity index 91%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLoggerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt
index 86529dc..7c9351c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.mobile.shared
+package com.android.systemui.statusbar.pipeline.mobile.data
import android.net.Network
import android.net.NetworkCapabilities
@@ -47,14 +47,14 @@
val expectedNetId = NET_1_ID.toString()
val expectedCaps = NET_1_CAPS.toString()
- assertThat(actualString).contains("true")
+ assertThat(actualString).contains("onDefaultCapabilitiesChanged")
assertThat(actualString).contains(expectedNetId)
assertThat(actualString).contains(expectedCaps)
}
@Test
fun testLogOnLost_bufferHasNetIdOfLostNetwork() {
- logger.logOnLost(NET_1)
+ logger.logOnLost(NET_1, isDefaultNetworkCallback = false)
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
@@ -62,6 +62,7 @@
val expectedNetId = NET_1_ID.toString()
+ assertThat(actualString).contains("onLost")
assertThat(actualString).contains(expectedNetId)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
deleted file mode 100644
index 45189cf..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
+++ /dev/null
@@ -1,106 +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.statusbar.pipeline.mobile.data.model
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.table.TableRowLogger
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ACTIVITY_DIRECTION_IN
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ACTIVITY_DIRECTION_OUT
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CARRIER_NETWORK_CHANGE
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CDMA_LEVEL
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CONNECTION_STATE
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_EMERGENCY
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_IS_GSM
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_OPERATOR
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_PRIMARY_LEVEL
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_RESOLVED_NETWORK_TYPE
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ROAMING
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-
-@SmallTest
-class MobileConnectionModelTest : SysuiTestCase() {
-
- @Test
- fun `log diff - initial log contains all columns`() {
- val logger = TestLogger()
- val connection = MobileConnectionModel()
-
- connection.logFull(logger)
-
- assertThat(logger.changes)
- .contains(Pair(COL_EMERGENCY, connection.isEmergencyOnly.toString()))
- assertThat(logger.changes).contains(Pair(COL_ROAMING, connection.isRoaming.toString()))
- assertThat(logger.changes)
- .contains(Pair(COL_OPERATOR, connection.operatorAlphaShort.toString()))
- assertThat(logger.changes).contains(Pair(COL_IS_GSM, connection.isGsm.toString()))
- assertThat(logger.changes).contains(Pair(COL_CDMA_LEVEL, connection.cdmaLevel.toString()))
- assertThat(logger.changes)
- .contains(Pair(COL_PRIMARY_LEVEL, connection.primaryLevel.toString()))
- assertThat(logger.changes)
- .contains(Pair(COL_CONNECTION_STATE, connection.dataConnectionState.toString()))
- assertThat(logger.changes)
- .contains(
- Pair(
- COL_ACTIVITY_DIRECTION_IN,
- connection.dataActivityDirection.hasActivityIn.toString(),
- )
- )
- assertThat(logger.changes)
- .contains(
- Pair(
- COL_ACTIVITY_DIRECTION_OUT,
- connection.dataActivityDirection.hasActivityOut.toString(),
- )
- )
- assertThat(logger.changes)
- .contains(
- Pair(COL_CARRIER_NETWORK_CHANGE, connection.carrierNetworkChangeActive.toString())
- )
- assertThat(logger.changes)
- .contains(Pair(COL_RESOLVED_NETWORK_TYPE, connection.resolvedNetworkType.toString()))
- }
-
- @Test
- fun `log diff - primary level changes - only level is logged`() {
- val logger = TestLogger()
- val connectionOld = MobileConnectionModel(primaryLevel = 1)
-
- val connectionNew = MobileConnectionModel(primaryLevel = 2)
-
- connectionNew.logDiffs(connectionOld, logger)
-
- assertThat(logger.changes).isEqualTo(listOf(Pair(COL_PRIMARY_LEVEL, "2")))
- }
-
- private class TestLogger : TableRowLogger {
- val changes = mutableListOf<Pair<String, String>>()
-
- override fun logChange(columnName: String, value: String?) {
- changes.add(Pair(columnName, value.toString()))
- }
-
- override fun logChange(columnName: String, value: Int) {
- changes.add(Pair(columnName, value.toString()))
- }
-
- override fun logChange(columnName: String, value: Boolean) {
- changes.add(Pair(columnName, value.toString()))
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
index 0145103..dfef62e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
@@ -24,8 +24,8 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 53cd71f1..44fbd5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -17,9 +17,11 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.flow.MutableStateFlow
// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionRepository
@@ -27,8 +29,19 @@
override val subId: Int,
override val tableLogBuffer: TableLogBuffer,
) : MobileConnectionRepository {
- private val _connectionInfo = MutableStateFlow(MobileConnectionModel())
- override val connectionInfo = _connectionInfo
+ override val isEmergencyOnly = MutableStateFlow(false)
+ override val isRoaming = MutableStateFlow(false)
+ override val operatorAlphaShort: MutableStateFlow<String?> = MutableStateFlow(null)
+ override val isInService = MutableStateFlow(false)
+ override val isGsm = MutableStateFlow(false)
+ override val cdmaLevel = MutableStateFlow(0)
+ override val primaryLevel = MutableStateFlow(0)
+ override val dataConnectionState = MutableStateFlow(DataConnectionState.Disconnected)
+ override val dataActivityDirection =
+ MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+ override val carrierNetworkChangeActive = MutableStateFlow(false)
+ override val resolvedNetworkType: MutableStateFlow<ResolvedNetworkType> =
+ MutableStateFlow(ResolvedNetworkType.UnknownNetworkType)
override val numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
@@ -40,10 +53,6 @@
override val networkName =
MutableStateFlow<NetworkNameModel>(NetworkNameModel.Default("default"))
- fun setConnectionInfo(model: MobileConnectionModel) {
- _connectionInfo.value = model
- }
-
fun setDataEnabled(enabled: Boolean) {
_dataEnabled.value = enabled
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 17502f2..07c8cee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -27,13 +27,13 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSource
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.validMobileEvent
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index b072dee..37fac34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -25,7 +25,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
@@ -36,8 +35,11 @@
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -123,34 +125,49 @@
assertConnection(underTest, networkModel)
}
- private fun assertConnection(
+ private fun TestScope.startCollection(conn: DemoMobileConnectionRepository): Job {
+ val job = launch {
+ launch { conn.cdmaLevel.collect {} }
+ launch { conn.primaryLevel.collect {} }
+ launch { conn.dataActivityDirection.collect {} }
+ launch { conn.carrierNetworkChangeActive.collect {} }
+ launch { conn.isRoaming.collect {} }
+ launch { conn.networkName.collect {} }
+ launch { conn.isEmergencyOnly.collect {} }
+ launch { conn.dataConnectionState.collect {} }
+ }
+ return job
+ }
+
+ private fun TestScope.assertConnection(
conn: DemoMobileConnectionRepository,
model: FakeNetworkEventModel
) {
+ val job = startCollection(underTest)
when (model) {
is FakeNetworkEventModel.Mobile -> {
- val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
assertThat(conn.subId).isEqualTo(model.subId)
- assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
- assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
- assertThat(connectionInfo.dataActivityDirection)
+ assertThat(conn.cdmaLevel.value).isEqualTo(model.level)
+ assertThat(conn.primaryLevel.value).isEqualTo(model.level)
+ assertThat(conn.dataActivityDirection.value)
.isEqualTo((model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel())
- assertThat(connectionInfo.carrierNetworkChangeActive)
+ assertThat(conn.carrierNetworkChangeActive.value)
.isEqualTo(model.carrierNetworkChange)
- assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
+ assertThat(conn.isRoaming.value).isEqualTo(model.roaming)
assertThat(conn.networkName.value)
.isEqualTo(NetworkNameModel.IntentDerived(model.name))
// TODO(b/261029387): check these once we start handling them
- assertThat(connectionInfo.isEmergencyOnly).isFalse()
- assertThat(connectionInfo.isGsm).isFalse()
- assertThat(connectionInfo.dataConnectionState)
- .isEqualTo(DataConnectionState.Connected)
+ assertThat(conn.isEmergencyOnly.value).isFalse()
+ assertThat(conn.isGsm.value).isFalse()
+ assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected)
}
// MobileDisabled isn't combinatorial in nature, and is tested in
// DemoMobileConnectionsRepositoryTest.kt
else -> {}
}
+
+ job.cancel()
}
/** Matches [FakeNetworkEventModel] */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index f60d92b..0e45d8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -26,7 +26,6 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
@@ -40,9 +39,11 @@
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -524,47 +525,65 @@
job.cancel()
}
- private fun assertConnection(
+ private fun TestScope.startCollection(conn: DemoMobileConnectionRepository): Job {
+ val job = launch {
+ launch { conn.cdmaLevel.collect {} }
+ launch { conn.primaryLevel.collect {} }
+ launch { conn.dataActivityDirection.collect {} }
+ launch { conn.carrierNetworkChangeActive.collect {} }
+ launch { conn.isRoaming.collect {} }
+ launch { conn.networkName.collect {} }
+ launch { conn.isEmergencyOnly.collect {} }
+ launch { conn.dataConnectionState.collect {} }
+ }
+ return job
+ }
+
+ private fun TestScope.assertConnection(
conn: DemoMobileConnectionRepository,
- model: FakeNetworkEventModel
+ model: FakeNetworkEventModel,
) {
+ val job = startCollection(conn)
+ // Assert the fields using the `MutableStateFlow` so that we don't have to start up
+ // a collector for every field for every test
when (model) {
is FakeNetworkEventModel.Mobile -> {
- val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
assertThat(conn.subId).isEqualTo(model.subId)
- assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
- assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
- assertThat(connectionInfo.dataActivityDirection)
+ assertThat(conn.cdmaLevel.value).isEqualTo(model.level)
+ assertThat(conn.primaryLevel.value).isEqualTo(model.level)
+ assertThat(conn.dataActivityDirection.value)
.isEqualTo((model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel())
- assertThat(connectionInfo.carrierNetworkChangeActive)
+ assertThat(conn.carrierNetworkChangeActive.value)
.isEqualTo(model.carrierNetworkChange)
- assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
+ assertThat(conn.isRoaming.value).isEqualTo(model.roaming)
assertThat(conn.networkName.value)
.isEqualTo(NetworkNameModel.IntentDerived(model.name))
// TODO(b/261029387) check these once we start handling them
- assertThat(connectionInfo.isEmergencyOnly).isFalse()
- assertThat(connectionInfo.isGsm).isFalse()
- assertThat(connectionInfo.dataConnectionState)
- .isEqualTo(DataConnectionState.Connected)
+ assertThat(conn.isEmergencyOnly.value).isFalse()
+ assertThat(conn.isGsm.value).isFalse()
+ assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected)
}
else -> {}
}
+
+ job.cancel()
}
- private fun assertCarrierMergedConnection(
+ private fun TestScope.assertCarrierMergedConnection(
conn: DemoMobileConnectionRepository,
model: FakeWifiEventModel.CarrierMerged,
) {
- val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
+ val job = startCollection(conn)
assertThat(conn.subId).isEqualTo(model.subscriptionId)
- assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
- assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
- assertThat(connectionInfo.carrierNetworkChangeActive).isEqualTo(false)
- assertThat(connectionInfo.isRoaming).isEqualTo(false)
- assertThat(connectionInfo.isEmergencyOnly).isFalse()
- assertThat(connectionInfo.isGsm).isFalse()
- assertThat(connectionInfo.dataConnectionState).isEqualTo(DataConnectionState.Connected)
+ assertThat(conn.cdmaLevel.value).isEqualTo(model.level)
+ assertThat(conn.primaryLevel.value).isEqualTo(model.level)
+ assertThat(conn.carrierNetworkChangeActive.value).isEqualTo(false)
+ assertThat(conn.isRoaming.value).isEqualTo(false)
+ assertThat(conn.isEmergencyOnly.value).isFalse()
+ assertThat(conn.isGsm.value).isFalse()
+ assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected)
+ job.cancel()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
index f0f213b..441186a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
@@ -22,7 +22,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -75,36 +74,48 @@
}
@Test
- fun connectionInfo_inactiveWifi_isDefault() =
+ fun inactiveWifi_isDefault() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latestConnState: DataConnectionState? = null
+ var latestNetType: ResolvedNetworkType? = null
+
+ val dataJob =
+ underTest.dataConnectionState.onEach { latestConnState = it }.launchIn(this)
+ val netJob = underTest.resolvedNetworkType.onEach { latestNetType = it }.launchIn(this)
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
- assertThat(latest).isEqualTo(MobileConnectionModel())
+ assertThat(latestConnState).isEqualTo(DataConnectionState.Disconnected)
+ assertThat(latestNetType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
- job.cancel()
+ dataJob.cancel()
+ netJob.cancel()
}
@Test
- fun connectionInfo_activeWifi_isDefault() =
+ fun activeWifi_isDefault() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latestConnState: DataConnectionState? = null
+ var latestNetType: ResolvedNetworkType? = null
+
+ val dataJob =
+ underTest.dataConnectionState.onEach { latestConnState = it }.launchIn(this)
+ val netJob = underTest.resolvedNetworkType.onEach { latestNetType = it }.launchIn(this)
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = NET_ID, level = 1))
- assertThat(latest).isEqualTo(MobileConnectionModel())
+ assertThat(latestConnState).isEqualTo(DataConnectionState.Disconnected)
+ assertThat(latestNetType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
- job.cancel()
+ dataJob.cancel()
+ netJob.cancel()
}
@Test
- fun connectionInfo_carrierMergedWifi_isValidAndFieldsComeFromWifiNetwork() =
+ fun carrierMergedWifi_isValidAndFieldsComeFromWifiNetwork() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Int? = null
+ val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
wifiRepository.setIsWifiEnabled(true)
wifiRepository.setIsWifiDefault(true)
@@ -117,34 +128,16 @@
)
)
- val expected =
- MobileConnectionModel(
- primaryLevel = 3,
- cdmaLevel = 3,
- dataConnectionState = DataConnectionState.Connected,
- dataActivityDirection =
- DataActivityModel(
- hasActivityIn = false,
- hasActivityOut = false,
- ),
- resolvedNetworkType = ResolvedNetworkType.CarrierMergedNetworkType,
- isRoaming = false,
- isEmergencyOnly = false,
- operatorAlphaShort = null,
- isInService = true,
- isGsm = false,
- carrierNetworkChangeActive = false,
- )
- assertThat(latest).isEqualTo(expected)
+ assertThat(latest).isEqualTo(3)
job.cancel()
}
@Test
- fun connectionInfo_activity_comesFromWifiActivity() =
+ fun activity_comesFromWifiActivity() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataActivityModel? = null
+ val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
wifiRepository.setIsWifiEnabled(true)
wifiRepository.setIsWifiDefault(true)
@@ -162,8 +155,8 @@
)
)
- assertThat(latest!!.dataActivityDirection.hasActivityIn).isTrue()
- assertThat(latest!!.dataActivityDirection.hasActivityOut).isFalse()
+ assertThat(latest!!.hasActivityIn).isTrue()
+ assertThat(latest!!.hasActivityOut).isFalse()
wifiRepository.setWifiActivity(
DataActivityModel(
@@ -172,17 +165,19 @@
)
)
- assertThat(latest!!.dataActivityDirection.hasActivityIn).isFalse()
- assertThat(latest!!.dataActivityDirection.hasActivityOut).isTrue()
+ assertThat(latest!!.hasActivityIn).isFalse()
+ assertThat(latest!!.hasActivityOut).isTrue()
job.cancel()
}
@Test
- fun connectionInfo_carrierMergedWifi_wrongSubId_isDefault() =
+ fun carrierMergedWifi_wrongSubId_isDefault() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latestLevel: Int? = null
+ var latestType: ResolvedNetworkType? = null
+ val levelJob = underTest.primaryLevel.onEach { latestLevel = it }.launchIn(this)
+ val typeJob = underTest.resolvedNetworkType.onEach { latestType = it }.launchIn(this)
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
@@ -192,20 +187,19 @@
)
)
- assertThat(latest).isEqualTo(MobileConnectionModel())
- assertThat(latest!!.primaryLevel).isNotEqualTo(3)
- assertThat(latest!!.resolvedNetworkType)
- .isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
+ assertThat(latestLevel).isNotEqualTo(3)
+ assertThat(latestType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
- job.cancel()
+ levelJob.cancel()
+ typeJob.cancel()
}
// This scenario likely isn't possible, but write a test for it anyway
@Test
- fun connectionInfo_carrierMergedButNotEnabled_isDefault() =
+ fun carrierMergedButNotEnabled_isDefault() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Int? = null
+ val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
@@ -216,17 +210,17 @@
)
wifiRepository.setIsWifiEnabled(false)
- assertThat(latest).isEqualTo(MobileConnectionModel())
+ assertThat(latest).isNotEqualTo(3)
job.cancel()
}
// This scenario likely isn't possible, but write a test for it anyway
@Test
- fun connectionInfo_carrierMergedButWifiNotDefault_isDefault() =
+ fun carrierMergedButWifiNotDefault_isDefault() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Int? = null
+ val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
@@ -237,7 +231,7 @@
)
wifiRepository.setIsWifiDefault(false)
- assertThat(latest).isEqualTo(MobileConnectionModel())
+ assertThat(latest).isNotEqualTo(3)
job.cancel()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index cd4d847..db5a7d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -24,13 +24,12 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_EMERGENCY
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_OPERATOR
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_PRIMARY_LEVEL
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_OPERATOR
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_PRIMARY_LEVEL
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
@@ -94,16 +93,16 @@
@Test
fun startingIsCarrierMerged_usesCarrierMergedInitially() =
testScope.runTest {
- val carrierMergedConnectionInfo =
- MobileConnectionModel(
- operatorAlphaShort = "Carrier Merged Operator",
- )
- carrierMergedRepo.setConnectionInfo(carrierMergedConnectionInfo)
+ val carrierMergedOperatorName = "Carrier Merged Operator"
+ val nonCarrierMergedName = "Non-carrier-merged"
+
+ carrierMergedRepo.operatorAlphaShort.value = carrierMergedOperatorName
+ mobileRepo.operatorAlphaShort.value = nonCarrierMergedName
initializeRepo(startingIsCarrierMerged = true)
assertThat(underTest.activeRepo.value).isEqualTo(carrierMergedRepo)
- assertThat(underTest.connectionInfo.value).isEqualTo(carrierMergedConnectionInfo)
+ assertThat(underTest.operatorAlphaShort.value).isEqualTo(carrierMergedOperatorName)
verify(mobileFactory, never())
.build(
SUB_ID,
@@ -116,16 +115,16 @@
@Test
fun startingNotCarrierMerged_usesTypicalInitially() =
testScope.runTest {
- val mobileConnectionInfo =
- MobileConnectionModel(
- operatorAlphaShort = "Typical Operator",
- )
- mobileRepo.setConnectionInfo(mobileConnectionInfo)
+ val carrierMergedOperatorName = "Carrier Merged Operator"
+ val nonCarrierMergedName = "Typical Operator"
+
+ carrierMergedRepo.operatorAlphaShort.value = carrierMergedOperatorName
+ mobileRepo.operatorAlphaShort.value = nonCarrierMergedName
initializeRepo(startingIsCarrierMerged = false)
assertThat(underTest.activeRepo.value).isEqualTo(mobileRepo)
- assertThat(underTest.connectionInfo.value).isEqualTo(mobileConnectionInfo)
+ assertThat(underTest.operatorAlphaShort.value).isEqualTo(nonCarrierMergedName)
verify(carrierMergedFactory, never()).build(SUB_ID, tableLogBuffer)
}
@@ -156,39 +155,40 @@
testScope.runTest {
initializeRepo(startingIsCarrierMerged = false)
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latestName: String? = null
+ var latestLevel: Int? = null
+
+ val nameJob = underTest.operatorAlphaShort.onEach { latestName = it }.launchIn(this)
+ val levelJob = underTest.primaryLevel.onEach { latestLevel = it }.launchIn(this)
underTest.setIsCarrierMerged(true)
- val info1 =
- MobileConnectionModel(
- operatorAlphaShort = "Carrier Merged Operator",
- primaryLevel = 1,
- )
- carrierMergedRepo.setConnectionInfo(info1)
+ val operator1 = "Carrier Merged Operator"
+ val level1 = 1
+ carrierMergedRepo.operatorAlphaShort.value = operator1
+ carrierMergedRepo.primaryLevel.value = level1
- assertThat(latest).isEqualTo(info1)
+ assertThat(latestName).isEqualTo(operator1)
+ assertThat(latestLevel).isEqualTo(level1)
- val info2 =
- MobileConnectionModel(
- operatorAlphaShort = "Carrier Merged Operator #2",
- primaryLevel = 2,
- )
- carrierMergedRepo.setConnectionInfo(info2)
+ val operator2 = "Carrier Merged Operator #2"
+ val level2 = 2
+ carrierMergedRepo.operatorAlphaShort.value = operator2
+ carrierMergedRepo.primaryLevel.value = level2
- assertThat(latest).isEqualTo(info2)
+ assertThat(latestName).isEqualTo(operator2)
+ assertThat(latestLevel).isEqualTo(level2)
- val info3 =
- MobileConnectionModel(
- operatorAlphaShort = "Carrier Merged Operator #3",
- primaryLevel = 3,
- )
- carrierMergedRepo.setConnectionInfo(info3)
+ val operator3 = "Carrier Merged Operator #3"
+ val level3 = 3
+ carrierMergedRepo.operatorAlphaShort.value = operator3
+ carrierMergedRepo.primaryLevel.value = level3
- assertThat(latest).isEqualTo(info3)
+ assertThat(latestName).isEqualTo(operator3)
+ assertThat(latestLevel).isEqualTo(level3)
- job.cancel()
+ nameJob.cancel()
+ levelJob.cancel()
}
@Test
@@ -196,39 +196,40 @@
testScope.runTest {
initializeRepo(startingIsCarrierMerged = false)
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latestName: String? = null
+ var latestLevel: Int? = null
+
+ val nameJob = underTest.operatorAlphaShort.onEach { latestName = it }.launchIn(this)
+ val levelJob = underTest.primaryLevel.onEach { latestLevel = it }.launchIn(this)
underTest.setIsCarrierMerged(false)
- val info1 =
- MobileConnectionModel(
- operatorAlphaShort = "Typical Merged Operator",
- primaryLevel = 1,
- )
- mobileRepo.setConnectionInfo(info1)
+ val operator1 = "Typical Merged Operator"
+ val level1 = 1
+ mobileRepo.operatorAlphaShort.value = operator1
+ mobileRepo.primaryLevel.value = level1
- assertThat(latest).isEqualTo(info1)
+ assertThat(latestName).isEqualTo(operator1)
+ assertThat(latestLevel).isEqualTo(level1)
- val info2 =
- MobileConnectionModel(
- operatorAlphaShort = "Typical Merged Operator #2",
- primaryLevel = 2,
- )
- mobileRepo.setConnectionInfo(info2)
+ val operator2 = "Typical Merged Operator #2"
+ val level2 = 2
+ mobileRepo.operatorAlphaShort.value = operator2
+ mobileRepo.primaryLevel.value = level2
- assertThat(latest).isEqualTo(info2)
+ assertThat(latestName).isEqualTo(operator2)
+ assertThat(latestLevel).isEqualTo(level2)
- val info3 =
- MobileConnectionModel(
- operatorAlphaShort = "Typical Merged Operator #3",
- primaryLevel = 3,
- )
- mobileRepo.setConnectionInfo(info3)
+ val operator3 = "Typical Merged Operator #3"
+ val level3 = 3
+ mobileRepo.operatorAlphaShort.value = operator3
+ mobileRepo.primaryLevel.value = level3
- assertThat(latest).isEqualTo(info3)
+ assertThat(latestName).isEqualTo(operator3)
+ assertThat(latestLevel).isEqualTo(level3)
- job.cancel()
+ nameJob.cancel()
+ levelJob.cancel()
}
@Test
@@ -236,57 +237,58 @@
testScope.runTest {
initializeRepo(startingIsCarrierMerged = false)
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latestName: String? = null
+ var latestLevel: Int? = null
- val carrierMergedInfo =
- MobileConnectionModel(
- operatorAlphaShort = "Carrier Merged Operator",
- primaryLevel = 4,
- )
- carrierMergedRepo.setConnectionInfo(carrierMergedInfo)
+ val nameJob = underTest.operatorAlphaShort.onEach { latestName = it }.launchIn(this)
+ val levelJob = underTest.primaryLevel.onEach { latestLevel = it }.launchIn(this)
- val mobileInfo =
- MobileConnectionModel(
- operatorAlphaShort = "Typical Operator",
- primaryLevel = 2,
- )
- mobileRepo.setConnectionInfo(mobileInfo)
+ val carrierMergedOperator = "Carrier Merged Operator"
+ val carrierMergedLevel = 4
+ carrierMergedRepo.operatorAlphaShort.value = carrierMergedOperator
+ carrierMergedRepo.primaryLevel.value = carrierMergedLevel
+
+ val mobileName = "Typical Operator"
+ val mobileLevel = 2
+ mobileRepo.operatorAlphaShort.value = mobileName
+ mobileRepo.primaryLevel.value = mobileLevel
// Start with the mobile info
- assertThat(latest).isEqualTo(mobileInfo)
+ assertThat(latestName).isEqualTo(mobileName)
+ assertThat(latestLevel).isEqualTo(mobileLevel)
// WHEN isCarrierMerged is set to true
underTest.setIsCarrierMerged(true)
// THEN the carrier merged info is used
- assertThat(latest).isEqualTo(carrierMergedInfo)
+ assertThat(latestName).isEqualTo(carrierMergedOperator)
+ assertThat(latestLevel).isEqualTo(carrierMergedLevel)
- val newCarrierMergedInfo =
- MobileConnectionModel(
- operatorAlphaShort = "New CM Operator",
- primaryLevel = 0,
- )
- carrierMergedRepo.setConnectionInfo(newCarrierMergedInfo)
+ val newCarrierMergedName = "New CM Operator"
+ val newCarrierMergedLevel = 0
+ carrierMergedRepo.operatorAlphaShort.value = newCarrierMergedName
+ carrierMergedRepo.primaryLevel.value = newCarrierMergedLevel
- assertThat(latest).isEqualTo(newCarrierMergedInfo)
+ assertThat(latestName).isEqualTo(newCarrierMergedName)
+ assertThat(latestLevel).isEqualTo(newCarrierMergedLevel)
// WHEN isCarrierMerged is set to false
underTest.setIsCarrierMerged(false)
// THEN the typical info is used
- assertThat(latest).isEqualTo(mobileInfo)
+ assertThat(latestName).isEqualTo(mobileName)
+ assertThat(latestLevel).isEqualTo(mobileLevel)
- val newMobileInfo =
- MobileConnectionModel(
- operatorAlphaShort = "New Mobile Operator",
- primaryLevel = 3,
- )
- mobileRepo.setConnectionInfo(newMobileInfo)
+ val newMobileName = "New MobileOperator"
+ val newMobileLevel = 3
+ mobileRepo.operatorAlphaShort.value = newMobileName
+ mobileRepo.primaryLevel.value = newMobileLevel
- assertThat(latest).isEqualTo(newMobileInfo)
+ assertThat(latestName).isEqualTo(newMobileName)
+ assertThat(latestLevel).isEqualTo(newMobileLevel)
- job.cancel()
+ nameJob.cancel()
+ levelJob.cancel()
}
@Test
@@ -370,7 +372,8 @@
initializeRepo(startingIsCarrierMerged = false)
- val job = underTest.connectionInfo.launchIn(this)
+ val emergencyJob = underTest.isEmergencyOnly.launchIn(this)
+ val operatorJob = underTest.operatorAlphaShort.launchIn(this)
// WHEN we set up some mobile connection info
val serviceState = ServiceState()
@@ -394,7 +397,8 @@
assertThat(dumpBuffer()).contains("$COL_OPERATOR${BUFFER_SEPARATOR}OpDiff")
assertThat(dumpBuffer()).contains("$COL_EMERGENCY${BUFFER_SEPARATOR}true")
- job.cancel()
+ emergencyJob.cancel()
+ operatorJob.cancel()
}
@Test
@@ -409,7 +413,7 @@
initializeRepo(startingIsCarrierMerged = true)
- val job = underTest.connectionInfo.launchIn(this)
+ val job = underTest.primaryLevel.launchIn(this)
// WHEN we set up carrier merged info
val networkId = 2
@@ -452,7 +456,7 @@
initializeRepo(startingIsCarrierMerged = false)
- val job = underTest.connectionInfo.launchIn(this)
+ val job = underTest.primaryLevel.launchIn(this)
// WHEN we set up some mobile connection info
val signalStrength = mock<SignalStrength>()
@@ -502,12 +506,7 @@
assertThat(bufferAfterCarrierMerged).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
// WHEN the normal network is updated
- val newMobileInfo =
- MobileConnectionModel(
- operatorAlphaShort = "Mobile Operator 2",
- primaryLevel = 0,
- )
- mobileRepo.setConnectionInfo(newMobileInfo)
+ mobileRepo.primaryLevel.value = 0
// THEN the new level is logged
assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}0")
@@ -529,7 +528,7 @@
// WHEN isCarrierMerged = false
initializeRepo(startingIsCarrierMerged = false)
- val job = underTest.connectionInfo.launchIn(this)
+ val job = underTest.primaryLevel.launchIn(this)
val signalStrength = mock<SignalStrength>()
whenever(signalStrength.level).thenReturn(1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index b2577e3..f6e5959 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -50,12 +50,15 @@
import android.telephony.TelephonyManager.EXTRA_SPN
import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest
+import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
@@ -65,7 +68,6 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
@@ -135,235 +137,285 @@
}
@Test
- fun testFlowForSubId_default() =
+ fun emergencyOnly() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
-
- assertThat(latest).isEqualTo(MobileConnectionModel())
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_emergencyOnly() =
- runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Boolean? = null
+ val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
val serviceState = ServiceState()
serviceState.isEmergencyOnly = true
getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
- assertThat(latest?.isEmergencyOnly).isEqualTo(true)
+ assertThat(latest).isEqualTo(true)
job.cancel()
}
@Test
- fun testFlowForSubId_emergencyOnly_toggles() =
+ fun emergencyOnly_toggles() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Boolean? = null
+ val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<ServiceStateListener>()
val serviceState = ServiceState()
serviceState.isEmergencyOnly = true
callback.onServiceStateChanged(serviceState)
+ assertThat(latest).isTrue()
+
serviceState.isEmergencyOnly = false
callback.onServiceStateChanged(serviceState)
- assertThat(latest?.isEmergencyOnly).isEqualTo(false)
+ assertThat(latest).isFalse()
job.cancel()
}
@Test
- fun testFlowForSubId_signalStrengths_levelsUpdate() =
+ fun cdmaLevelUpdates() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Int? = null
+ val job = underTest.cdmaLevel.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
- val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+ var strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
callback.onSignalStrengthsChanged(strength)
- assertThat(latest?.isGsm).isEqualTo(true)
- assertThat(latest?.primaryLevel).isEqualTo(1)
- assertThat(latest?.cdmaLevel).isEqualTo(2)
+ assertThat(latest).isEqualTo(2)
+
+ // gsmLevel updates, no change to cdmaLevel
+ strength = signalStrength(gsmLevel = 3, cdmaLevel = 2, isGsm = true)
+
+ assertThat(latest).isEqualTo(2)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_connected() =
+ fun gsmLevelUpdates() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Int? = null
+ val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+ var strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isEqualTo(1)
+
+ strength = signalStrength(gsmLevel = 3, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isEqualTo(3)
+
+ job.cancel()
+ }
+
+ @Test
+ fun isGsm() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isGsm.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+ var strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isTrue()
+
+ strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = false)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun dataConnectionState_connected() =
+ runBlocking(IMMEDIATE) {
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_CONNECTED, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Connected)
+ assertThat(latest).isEqualTo(DataConnectionState.Connected)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_connecting() =
+ fun dataConnectionState_connecting() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_CONNECTING, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Connecting)
+ assertThat(latest).isEqualTo(DataConnectionState.Connecting)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_disconnected() =
+ fun dataConnectionState_disconnected() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_DISCONNECTED, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Disconnected)
+ assertThat(latest).isEqualTo(DataConnectionState.Disconnected)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_disconnecting() =
+ fun dataConnectionState_disconnecting() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_DISCONNECTING, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Disconnecting)
+ assertThat(latest).isEqualTo(DataConnectionState.Disconnecting)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_suspended() =
+ fun dataConnectionState_suspended() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_SUSPENDED, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Suspended)
+ assertThat(latest).isEqualTo(DataConnectionState.Suspended)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_handoverInProgress() =
+ fun dataConnectionState_handoverInProgress() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_HANDOVER_IN_PROGRESS, 200 /* unused */)
- assertThat(latest?.dataConnectionState)
- .isEqualTo(DataConnectionState.HandoverInProgress)
+ assertThat(latest).isEqualTo(DataConnectionState.HandoverInProgress)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_unknown() =
+ fun dataConnectionState_unknown() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_UNKNOWN, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Unknown)
+ assertThat(latest).isEqualTo(DataConnectionState.Unknown)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_invalid() =
+ fun dataConnectionState_invalid() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(45, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Invalid)
+ assertThat(latest).isEqualTo(DataConnectionState.Invalid)
job.cancel()
}
@Test
- fun testFlowForSubId_dataActivity() =
+ fun dataActivity() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataActivityModel? = null
+ val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<DataActivityListener>()
callback.onDataActivity(DATA_ACTIVITY_INOUT)
- assertThat(latest?.dataActivityDirection)
- .isEqualTo(DATA_ACTIVITY_INOUT.toMobileDataActivityModel())
+ assertThat(latest).isEqualTo(DATA_ACTIVITY_INOUT.toMobileDataActivityModel())
job.cancel()
}
@Test
- fun testFlowForSubId_carrierNetworkChange() =
+ fun carrierNetworkChange() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Boolean? = null
+ val job = underTest.carrierNetworkChangeActive.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.CarrierNetworkListener>()
callback.onCarrierNetworkChange(true)
- assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
+ assertThat(latest).isEqualTo(true)
job.cancel()
}
@Test
- fun subscriptionFlow_networkType_default() =
+ fun networkType_default() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: ResolvedNetworkType? = null
+ val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
val expected = UnknownNetworkType
- assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+ assertThat(latest).isEqualTo(expected)
job.cancel()
}
@Test
- fun subscriptionFlow_networkType_updatesUsingDefault() =
+ fun networkType_unknown_hasCorrectKey() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: ResolvedNetworkType? = null
+ val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val type = NETWORK_TYPE_UNKNOWN
+ val expected = UnknownNetworkType
+ val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
+ callback.onDisplayInfoChanged(ti)
+
+ assertThat(latest).isEqualTo(expected)
+ assertThat(latest!!.lookupKey).isEqualTo(MobileMappings.toIconKey(type))
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_updatesUsingDefault() =
+ runBlocking(IMMEDIATE) {
+ var latest: ResolvedNetworkType? = null
+ val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val type = NETWORK_TYPE_LTE
@@ -371,16 +423,16 @@
val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
callback.onDisplayInfoChanged(ti)
- assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+ assertThat(latest).isEqualTo(expected)
job.cancel()
}
@Test
- fun subscriptionFlow_networkType_updatesUsingOverride() =
+ fun networkType_updatesUsingOverride() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: ResolvedNetworkType? = null
+ val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val type = OVERRIDE_NETWORK_TYPE_LTE_CA
@@ -392,7 +444,7 @@
}
callback.onDisplayInfoChanged(ti)
- assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+ assertThat(latest).isEqualTo(expected)
job.cancel()
}
@@ -466,7 +518,7 @@
fun `roaming - gsm - queries service state`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
- val job = underTest.connectionInfo.onEach { latest = it.isRoaming }.launchIn(this)
+ val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
val serviceState = ServiceState()
serviceState.roaming = false
@@ -492,8 +544,7 @@
fun `activity - updates from callback`() =
runBlocking(IMMEDIATE) {
var latest: DataActivityModel? = null
- val job =
- underTest.connectionInfo.onEach { latest = it.dataActivityDirection }.launchIn(this)
+ val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
assertThat(latest)
.isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
@@ -611,8 +662,7 @@
runBlocking(IMMEDIATE) {
var latest: String? = null
- val job =
- underTest.connectionInfo.onEach { latest = it.operatorAlphaShort }.launchIn(this)
+ val job = underTest.operatorAlphaShort.onEach { latest = it }.launchIn(this)
val shortName = "short name"
val serviceState = ServiceState()
@@ -633,7 +683,7 @@
fun `connection model - isInService - not iwlan`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
- val job = underTest.connectionInfo.onEach { latest = it.isInService }.launchIn(this)
+ val job = underTest.isInService.onEach { latest = it }.launchIn(this)
val serviceState = ServiceState()
serviceState.voiceRegState = STATE_IN_SERVICE
@@ -658,7 +708,7 @@
fun `connection model - isInService - is iwlan - voice out of service - data in service`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
- val job = underTest.connectionInfo.onEach { latest = it.isInService }.launchIn(this)
+ val job = underTest.isInService.onEach { latest = it }.launchIn(this)
// Mock the service state here so we can make it specifically IWLAN
val serviceState: ServiceState = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 09b7a66..68b1cda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -37,12 +37,12 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index fa072fc..1eb1056 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -23,7 +23,6 @@
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.CarrierMergedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
@@ -74,9 +73,7 @@
@Test
fun gsm_level_default_unknown() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(isGsm = true),
- )
+ connectionRepository.isGsm.value = true
var latest: Int? = null
val job = underTest.level.onEach { latest = it }.launchIn(this)
@@ -89,13 +86,9 @@
@Test
fun gsm_usesGsmLevel() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = true,
- primaryLevel = GSM_LEVEL,
- cdmaLevel = CDMA_LEVEL
- ),
- )
+ connectionRepository.isGsm.value = true
+ connectionRepository.primaryLevel.value = GSM_LEVEL
+ connectionRepository.cdmaLevel.value = CDMA_LEVEL
var latest: Int? = null
val job = underTest.level.onEach { latest = it }.launchIn(this)
@@ -108,13 +101,9 @@
@Test
fun gsm_alwaysShowCdmaTrue_stillUsesGsmLevel() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = true,
- primaryLevel = GSM_LEVEL,
- cdmaLevel = CDMA_LEVEL,
- ),
- )
+ connectionRepository.isGsm.value = true
+ connectionRepository.primaryLevel.value = GSM_LEVEL
+ connectionRepository.cdmaLevel.value = CDMA_LEVEL
mobileIconsInteractor.alwaysUseCdmaLevel.value = true
var latest: Int? = null
@@ -128,9 +117,7 @@
@Test
fun notGsm_level_default_unknown() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(isGsm = false),
- )
+ connectionRepository.isGsm.value = false
var latest: Int? = null
val job = underTest.level.onEach { latest = it }.launchIn(this)
@@ -142,13 +129,9 @@
@Test
fun notGsm_alwaysShowCdmaTrue_usesCdmaLevel() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = false,
- primaryLevel = GSM_LEVEL,
- cdmaLevel = CDMA_LEVEL
- ),
- )
+ connectionRepository.isGsm.value = false
+ connectionRepository.primaryLevel.value = GSM_LEVEL
+ connectionRepository.cdmaLevel.value = CDMA_LEVEL
mobileIconsInteractor.alwaysUseCdmaLevel.value = true
var latest: Int? = null
@@ -162,13 +145,9 @@
@Test
fun notGsm_alwaysShowCdmaFalse_usesPrimaryLevel() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = false,
- primaryLevel = GSM_LEVEL,
- cdmaLevel = CDMA_LEVEL,
- ),
- )
+ connectionRepository.isGsm.value = false
+ connectionRepository.primaryLevel.value = GSM_LEVEL
+ connectionRepository.cdmaLevel.value = CDMA_LEVEL
mobileIconsInteractor.alwaysUseCdmaLevel.value = false
var latest: Int? = null
@@ -197,11 +176,8 @@
@Test
fun iconGroup_three_g() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
- ),
- )
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -214,23 +190,14 @@
@Test
fun iconGroup_updates_on_change() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
- ),
- )
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType =
- DefaultNetworkType(
- mobileMappingsProxy.toIconKey(FOUR_G),
- ),
- ),
- )
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(FOUR_G))
yield()
assertThat(latest).isEqualTo(TelephonyIcons.FOUR_G)
@@ -241,12 +208,8 @@
@Test
fun iconGroup_5g_override_type() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType =
- OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE))
- ),
- )
+ connectionRepository.resolvedNetworkType.value =
+ OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE))
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -259,12 +222,8 @@
@Test
fun iconGroup_default_if_no_lookup() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType =
- DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN)),
- ),
- )
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN))
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -277,11 +236,7 @@
@Test
fun iconGroup_carrierMerged_usesOverride() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType = CarrierMergedNetworkType,
- ),
- )
+ connectionRepository.resolvedNetworkType.value = CarrierMergedNetworkType
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -295,11 +250,8 @@
fun `icon group - checks default data`() =
runBlocking(IMMEDIATE) {
mobileIconsInteractor.defaultDataSubId.value = SUB_1_ID
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
- ),
- )
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -380,9 +332,7 @@
var latest: Boolean? = null
val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(dataConnectionState = DataConnectionState.Connected)
- )
+ connectionRepository.dataConnectionState.value = DataConnectionState.Connected
yield()
assertThat(latest).isTrue()
@@ -396,9 +346,7 @@
var latest: Boolean? = null
val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(dataConnectionState = DataConnectionState.Disconnected)
- )
+ connectionRepository.dataConnectionState.value = DataConnectionState.Disconnected
assertThat(latest).isFalse()
@@ -411,11 +359,11 @@
var latest: Boolean? = null
val job = underTest.isInService.onEach { latest = it }.launchIn(this)
- connectionRepository.setConnectionInfo(MobileConnectionModel(isInService = true))
+ connectionRepository.isInService.value = true
assertThat(latest).isTrue()
- connectionRepository.setConnectionInfo(MobileConnectionModel(isInService = false))
+ connectionRepository.isInService.value = false
assertThat(latest).isFalse()
@@ -429,22 +377,13 @@
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
connectionRepository.cdmaRoaming.value = true
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = true,
- isRoaming = false,
- )
- )
+ connectionRepository.isGsm.value = true
+ connectionRepository.isRoaming.value = false
yield()
assertThat(latest).isFalse()
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = true,
- isRoaming = true,
- )
- )
+ connectionRepository.isRoaming.value = true
yield()
assertThat(latest).isTrue()
@@ -459,23 +398,15 @@
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
connectionRepository.cdmaRoaming.value = false
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = false,
- isRoaming = true,
- )
- )
+ connectionRepository.isGsm.value = false
+ connectionRepository.isRoaming.value = true
yield()
assertThat(latest).isFalse()
connectionRepository.cdmaRoaming.value = true
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = false,
- isRoaming = false,
- )
- )
+ connectionRepository.isGsm.value = false
+ connectionRepository.isRoaming.value = false
yield()
assertThat(latest).isTrue()
@@ -490,25 +421,15 @@
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
connectionRepository.cdmaRoaming.value = true
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = false,
- isRoaming = true,
- carrierNetworkChangeActive = true,
- )
- )
+ connectionRepository.isGsm.value = false
+ connectionRepository.isRoaming.value = true
+ connectionRepository.carrierNetworkChangeActive.value = true
yield()
assertThat(latest).isFalse()
connectionRepository.cdmaRoaming.value = true
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = true,
- isRoaming = true,
- carrierNetworkChangeActive = true,
- )
- )
+ connectionRepository.isGsm.value = true
yield()
assertThat(latest).isFalse()
@@ -526,24 +447,20 @@
// Default network name, operator name is non-null, uses the operator name
connectionRepository.networkName.value = DEFAULT_NAME
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(operatorAlphaShort = testOperatorName)
- )
+ connectionRepository.operatorAlphaShort.value = testOperatorName
yield()
assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived(testOperatorName))
// Default network name, operator name is null, uses the default
- connectionRepository.setConnectionInfo(MobileConnectionModel(operatorAlphaShort = null))
+ connectionRepository.operatorAlphaShort.value = null
yield()
assertThat(latest).isEqualTo(DEFAULT_NAME)
// Derived network name, operator name non-null, uses the derived name
connectionRepository.networkName.value = DERIVED_NAME
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(operatorAlphaShort = testOperatorName)
- )
+ connectionRepository.operatorAlphaShort.value = testOperatorName
yield()
assertThat(latest).isEqualTo(DERIVED_NAME)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
new file mode 100644
index 0000000..4aa48d6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 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.pipeline.mobile.ui
+
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.KeyguardMobileIconViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MobileViewLoggerTest : SysuiTestCase() {
+ private val buffer = LogBufferFactory(DumpManager(), mock()).create("buffer", 10)
+ private val stringWriter = StringWriter()
+ private val printWriter = PrintWriter(stringWriter)
+
+ private val underTest = MobileViewLogger(buffer, mock())
+
+ @Mock private lateinit var flags: StatusBarPipelineFlags
+ @Mock private lateinit var commonViewModel: MobileIconViewModel
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun collectionStarted_dumpHasInfo() {
+ val view = TextView(context)
+ val viewModel = QsMobileIconViewModel(commonViewModel, flags)
+
+ underTest.logCollectionStarted(view, viewModel)
+
+ val dumpString = getDumpString()
+ assertThat(dumpString).contains("${view.getIdForLogging()}, isCollecting=true")
+ }
+
+ @Test
+ fun collectionStarted_multipleViews_dumpHasInfo() {
+ val view = TextView(context)
+ val view2 = TextView(context)
+ val viewModel = QsMobileIconViewModel(commonViewModel, flags)
+ val viewModel2 = KeyguardMobileIconViewModel(commonViewModel, flags)
+
+ underTest.logCollectionStarted(view, viewModel)
+ underTest.logCollectionStarted(view2, viewModel2)
+
+ val dumpString = getDumpString()
+ assertThat(dumpString).contains("${view.getIdForLogging()}, isCollecting=true")
+ assertThat(dumpString).contains("${view2.getIdForLogging()}, isCollecting=true")
+ }
+
+ @Test
+ fun collectionStopped_dumpHasInfo() {
+ val view = TextView(context)
+ val view2 = TextView(context)
+ val viewModel = QsMobileIconViewModel(commonViewModel, flags)
+ val viewModel2 = KeyguardMobileIconViewModel(commonViewModel, flags)
+
+ underTest.logCollectionStarted(view, viewModel)
+ underTest.logCollectionStarted(view2, viewModel2)
+ underTest.logCollectionStopped(view, viewModel)
+
+ val dumpString = getDumpString()
+ assertThat(dumpString).contains("${view.getIdForLogging()}, isCollecting=false")
+ assertThat(dumpString).contains("${view2.getIdForLogging()}, isCollecting=true")
+ }
+
+ private fun getDumpString(): String {
+ underTest.dump(printWriter, args = arrayOf())
+ return stringWriter.toString()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
index e68a397..7420db2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel
@@ -60,6 +61,7 @@
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var tableLogBuffer: TableLogBuffer
+ @Mock private lateinit var viewLogger: MobileViewLogger
@Mock private lateinit var constants: ConnectivityConstants
private lateinit var interactor: FakeMobileIconInteractor
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
@@ -94,7 +96,13 @@
@Test
fun setVisibleState_icon_iconShownDotHidden() {
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
view.setVisibleState(StatusBarIconView.STATE_ICON, /* animate= */ false)
@@ -109,8 +117,13 @@
@Test
fun setVisibleState_dot_iconHiddenDotShown() {
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
-
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
view.setVisibleState(StatusBarIconView.STATE_DOT, /* animate= */ false)
ViewUtils.attachView(view)
@@ -124,8 +137,13 @@
@Test
fun setVisibleState_hidden_iconAndDotHidden() {
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
-
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
view.setVisibleState(StatusBarIconView.STATE_HIDDEN, /* animate= */ false)
ViewUtils.attachView(view)
@@ -142,8 +160,13 @@
whenever(constants.hasDataCapabilities).thenReturn(false)
createViewModel()
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
-
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -157,8 +180,13 @@
whenever(constants.hasDataCapabilities).thenReturn(true)
createViewModel()
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
-
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -171,8 +199,13 @@
fun isIconVisible_notAirplaneMode_outputsTrue() {
airplaneModeRepository.setIsAirplaneMode(false)
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
-
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -185,8 +218,13 @@
fun isIconVisible_airplaneMode_outputsTrue() {
airplaneModeRepository.setIsAirplaneMode(true)
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
-
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -198,7 +236,13 @@
@Test
fun onDarkChanged_iconHasNewColor() {
whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -214,7 +258,13 @@
@Test
fun setStaticDrawableColor_iconHasNewColor() {
whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
ViewUtils.attachView(view)
testableLooper.processAllMessages()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index f983030..a6d9152 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -84,7 +85,7 @@
testScope.backgroundScope,
)
- homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+ homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags, mock())
qsIcon = QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
keyguardIcon = KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index 4628f84..ddb7f4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -24,6 +24,8 @@
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
+import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
@@ -51,6 +53,8 @@
private lateinit var airplaneModeInteractor: AirplaneModeInteractor
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var constants: ConnectivityConstants
+ @Mock private lateinit var logger: MobileViewLogger
+ @Mock private lateinit var verboseLogger: VerboseMobileViewLogger
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -73,6 +77,8 @@
underTest =
MobileIconsViewModel(
subscriptionIdsFlow,
+ logger,
+ verboseLogger,
interactor,
airplaneModeInteractor,
constants,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
index e4c8fd0..b4039d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
@@ -164,6 +164,10 @@
override fun getShouldIconBeVisible(): Boolean {
return shouldIconBeVisibleInternal
}
+
+ override fun isCollecting(): Boolean {
+ return true
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
index 48b1732..481d453 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
@@ -38,6 +38,7 @@
import com.android.internal.view.RotationPolicy;
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.wrapper.RotationPolicyWrapper;
@@ -55,10 +56,12 @@
private static final String[] DEFAULT_SETTINGS = new String[]{"0:1", "2:0:1", "1:2"};
+ @Mock private DeviceStateManager mDeviceStateManager;
+ @Mock private DeviceStateRotationLockSettingControllerLogger mLogger;
+ @Mock private DumpManager mDumpManager;
+
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
- @Mock
- private DeviceStateManager mDeviceStateManager;
private final RotationPolicyWrapper mFakeRotationPolicy = new FakeRotationPolicy();
private DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
@@ -78,7 +81,13 @@
mSettingsManager = DeviceStateRotationLockSettingsManager.getInstance(mContext);
mDeviceStateRotationLockSettingController =
new DeviceStateRotationLockSettingController(
- mFakeRotationPolicy, mDeviceStateManager, mFakeExecutor, mSettingsManager);
+ mFakeRotationPolicy,
+ mDeviceStateManager,
+ mFakeExecutor,
+ mSettingsManager,
+ mLogger,
+ mDumpManager
+ );
mDeviceStateRotationLockSettingController.setListening(true);
verify(mDeviceStateManager)
@@ -173,15 +182,11 @@
}
@Test
- public void whenDeviceStateSwitchedToIgnoredState_usePreviousSetting() {
- initializeSettingsWith(
- 0, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
- mFakeRotationPolicy.setRotationLock(true);
-
- mDeviceStateCallback.onStateChanged(1);
- assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
-
+ public void whenDeviceStateSwitchedToIgnoredState_useFallbackSetting() {
mDeviceStateCallback.onStateChanged(0);
+ assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
+
+ mDeviceStateCallback.onStateChanged(2);
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index f9b5767..17b5e05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -33,6 +33,7 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.app.UiModeManager;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
@@ -47,7 +48,6 @@
import android.os.UserManager;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
-import android.view.accessibility.AccessibilityManager;
import androidx.annotation.VisibleForTesting;
import androidx.test.filters.SmallTest;
@@ -116,7 +116,7 @@
@Mock
private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
- private AccessibilityManager mAccessibilityManager;
+ private UiModeManager mUiModeManager;
@Captor
private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiver;
@Captor
@@ -135,7 +135,7 @@
MockitoAnnotations.initMocks(this);
when(mFeatureFlags.isEnabled(Flags.MONET)).thenReturn(true);
when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE);
- when(mAccessibilityManager.getUiContrast()).thenReturn(0.5f);
+ when(mUiModeManager.getContrast()).thenReturn(0.5f);
when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
when(mResources.getColor(eq(android.R.color.system_accent1_500), any()))
.thenReturn(Color.RED);
@@ -151,7 +151,7 @@
mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier,
mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
- mAccessibilityManager) {
+ mUiModeManager) {
@VisibleForTesting
protected boolean isNightMode() {
return false;
@@ -733,7 +733,7 @@
mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier,
mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
- mAccessibilityManager) {
+ mUiModeManager) {
@VisibleForTesting
protected boolean isNightMode() {
return false;
@@ -773,7 +773,7 @@
mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier,
mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
- mAccessibilityManager) {
+ mUiModeManager) {
@VisibleForTesting
protected boolean isNightMode() {
return false;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index f0a53ae..8d74c82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -29,6 +29,8 @@
import android.provider.Settings
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.R
@@ -63,6 +65,7 @@
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertNotNull
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -73,6 +76,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
@@ -98,6 +102,7 @@
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
@Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private lateinit var underTest: UserInteractor
@@ -156,6 +161,7 @@
repository = telephonyRepository,
),
broadcastDispatcher = fakeBroadcastDispatcher,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
@@ -180,6 +186,18 @@
}
@Test
+ fun `testKeyguardUpdateMonitor_onKeyguardGoingAway`() =
+ testScope.runTest {
+ val argumentCaptor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
+ verify(keyguardUpdateMonitor).registerCallback(argumentCaptor.capture())
+
+ argumentCaptor.value.onKeyguardGoingAway()
+
+ val lastValue = collectLastValue(underTest.dialogDismissRequests)
+ assertNotNull(lastValue)
+ }
+
+ @Test
fun `onRecordSelected - user`() =
testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index bc0881c..9b74c1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -25,6 +25,7 @@
import android.os.UserManager
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
@@ -80,6 +81,7 @@
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
@Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private lateinit var underTest: StatusBarUserChipViewModel
@@ -263,6 +265,7 @@
repository = FakeTelephonyRepository(),
),
broadcastDispatcher = fakeBroadcastDispatcher,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index c8b0496..7780a43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -23,6 +23,7 @@
import android.os.UserManager
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
@@ -81,6 +82,7 @@
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
@Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private lateinit var underTest: UserSwitcherViewModel
@@ -165,6 +167,7 @@
repository = FakeTelephonyRepository(),
),
broadcastDispatcher = fakeBroadcastDispatcher,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
index a079668..c3a6cf0 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
@@ -27,6 +27,7 @@
import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.updates.RotationChangeProvider
+import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix
@@ -67,6 +68,7 @@
}
val unfoldTransitionProvider: Optional<UnfoldTransitionProgressProvider>
+ val hingeAngleProvider: HingeAngleProvider
val rotationChangeProvider: RotationChangeProvider
}
diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk
index 69641e6..cbca3f0 100644
--- a/packages/overlays/Android.mk
+++ b/packages/overlays/Android.mk
@@ -31,6 +31,7 @@
NavigationBarModeGesturalOverlayNarrowBack \
NavigationBarModeGesturalOverlayWideBack \
NavigationBarModeGesturalOverlayExtraWideBack \
+ TransparentNavigationBarOverlay \
preinstalled-packages-platform-overlays.xml
include $(BUILD_PHONY_PACKAGE)
diff --git a/packages/overlays/TransparentNavigationBarOverlay/Android.bp b/packages/overlays/TransparentNavigationBarOverlay/Android.bp
new file mode 100644
index 0000000..43dc82b
--- /dev/null
+++ b/packages/overlays/TransparentNavigationBarOverlay/Android.bp
@@ -0,0 +1,30 @@
+//
+// Copyright 2023, 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"],
+}
+
+runtime_resource_overlay {
+ name: "TransparentNavigationBarOverlay",
+ theme: "TransparentNavigationBar",
+ product_specific: true,
+}
diff --git a/packages/overlays/TransparentNavigationBarOverlay/AndroidManifest.xml b/packages/overlays/TransparentNavigationBarOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..d69abfa
--- /dev/null
+++ b/packages/overlays/TransparentNavigationBarOverlay/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<!--
+/**
+ * Copyright (c) 2023, 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.internal.systemui.navbar.transparent"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <overlay android:targetPackage="android"
+ android:priority="1"/>
+
+ <application android:label="@string/transparent_navigation_bar_title" android:hasCode="false"/>
+</manifest>
\ No newline at end of file
diff --git a/packages/overlays/TransparentNavigationBarOverlay/res/values/config.xml b/packages/overlays/TransparentNavigationBarOverlay/res/values/config.xml
new file mode 100644
index 0000000..3b358ea
--- /dev/null
+++ b/packages/overlays/TransparentNavigationBarOverlay/res/values/config.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2023, 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>
+ <!-- Controls whether the navigation bar background color provided by the app is transparent by
+ default. This should be controlled in developer options. -->
+ <bool name="config_navBarDefaultTransparent">true</bool>
+</resources>
\ No newline at end of file
diff --git a/packages/overlays/TransparentNavigationBarOverlay/res/values/strings.xml b/packages/overlays/TransparentNavigationBarOverlay/res/values/strings.xml
new file mode 100644
index 0000000..0e3462e
--- /dev/null
+++ b/packages/overlays/TransparentNavigationBarOverlay/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2023, 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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Name of overlay [CHAR LIMIT=64] -->
+ <string name="transparent_navigation_bar_title" translatable="false">Transparent navigation bar</string>
+</resources>
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 7fba72b..6503595 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -26,11 +26,8 @@
import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
-import static android.provider.Settings.Secure.CONTRAST_LEVEL;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
-import static android.view.accessibility.AccessibilityManager.CONTRAST_DEFAULT_VALUE;
-import static android.view.accessibility.AccessibilityManager.CONTRAST_NOT_SET;
import static android.view.accessibility.AccessibilityManager.FlashNotificationReason;
import static android.view.accessibility.AccessibilityManager.ShortcutType;
@@ -1997,16 +1994,6 @@
return false;
}
- private boolean readUiContrastLocked(AccessibilityUserState userState) {
- float contrast = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
- CONTRAST_LEVEL, CONTRAST_DEFAULT_VALUE, userState.mUserId);
- if (Math.abs(userState.getUiContrastLocked() - contrast) >= 1e-10) {
- userState.setUiContrastLocked(contrast);
- return true;
- }
- return false;
- }
-
/**
* Performs {@link AccessibilityService}s delayed notification. The delay is configurable
* and denotes the period after the last event before notifying the service.
@@ -2676,7 +2663,6 @@
somethingChanged |= readMagnificationCapabilitiesLocked(userState);
somethingChanged |= readMagnificationFollowTypingLocked(userState);
somethingChanged |= readAlwaysOnMagnificationLocked(userState);
- somethingChanged |= readUiContrastLocked(userState);
return somethingChanged;
}
@@ -3851,19 +3837,6 @@
return mProxyManager.isProxyed(displayId);
}
- @Override public float getUiContrast() {
- if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
- mTraceManager.logTrace(LOG_TAG + ".getUiContrast", FLAGS_ACCESSIBILITY_MANAGER);
- }
- synchronized (mLock) {
- AccessibilityUserState userState = getCurrentUserStateLocked();
- float contrast = userState.getUiContrastLocked();
- if (contrast != CONTRAST_NOT_SET) return contrast;
- readUiContrastLocked(userState);
- return userState.getUiContrastLocked();
- }
- }
-
@Override
public boolean startFlashNotificationSequence(String opPkg,
@FlashNotificationReason int reason, IBinder token) {
@@ -4350,9 +4323,6 @@
private final Uri mAlwaysOnMagnificationUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED);
- private final Uri mUiContrastUri = Settings.Secure.getUriFor(
- CONTRAST_LEVEL);
-
public AccessibilityContentObserver(Handler handler) {
super(handler);
}
@@ -4395,8 +4365,6 @@
mMagnificationFollowTypingUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
mAlwaysOnMagnificationUri, false, this, UserHandle.USER_ALL);
- contentResolver.registerContentObserver(
- mUiContrastUri, false, this, UserHandle.USER_ALL);
}
@Override
@@ -4468,10 +4436,6 @@
readMagnificationFollowTypingLocked(userState);
} else if (mAlwaysOnMagnificationUri.equals(uri)) {
readAlwaysOnMagnificationLocked(userState);
- } else if (mUiContrastUri.equals(uri)) {
- if (readUiContrastLocked(userState)) {
- updateUiContrastLocked(userState);
- }
}
}
}
@@ -4788,22 +4752,7 @@
userState.getFocusColorLocked());
}));
});
- }
- private void updateUiContrastLocked(AccessibilityUserState userState) {
- if (userState.mUserId != mCurrentUserId) {
- return;
- }
- if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
- mTraceManager.logTrace(LOG_TAG + ".updateUiContrastLocked",
- FLAGS_ACCESSIBILITY_SERVICE_CLIENT, "userState=" + userState);
- }
- float contrast = userState.getUiContrastLocked();
- mMainHandler.post(() -> {
- broadcastToClients(userState, ignoreRemoteException(client -> {
- client.mCallback.setUiContrast(contrast);
- }));
- });
}
public AccessibilityTraceManager getTraceManager() {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 1c9ce3c..3b169f8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -26,8 +26,6 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
-import static android.view.accessibility.AccessibilityManager.CONTRAST_DEFAULT_VALUE;
-import static android.view.accessibility.AccessibilityManager.CONTRAST_NOT_SET;
import static android.view.accessibility.AccessibilityManager.ShortcutType;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
@@ -147,8 +145,6 @@
private final int mFocusStrokeWidthDefaultValue;
// The default value of the focus color.
private final int mFocusColorDefaultValue;
- /** The color contrast in [-1, 1] */
- private float mUiContrast = CONTRAST_DEFAULT_VALUE;
private Context mContext;
@@ -224,7 +220,6 @@
mFocusColor = mFocusColorDefaultValue;
mMagnificationFollowTypingEnabled = true;
mAlwaysOnMagnificationEnabled = false;
- mUiContrast = CONTRAST_NOT_SET;
}
void addServiceLocked(AccessibilityServiceConnection serviceConnection) {
@@ -1001,7 +996,6 @@
return mFocusColor;
}
-
/**
* Sets the stroke width and color of the focus rectangle.
*
@@ -1027,20 +1021,4 @@
}
return false;
}
-
- /**
- * Get the color contrast
- * @return color contrast in [-1, 1]
- */
- public float getUiContrastLocked() {
- return mUiContrast;
- }
-
- /**
- * Set the color contrast
- * @param contrast the new color contrast in [-1, 1]
- */
- public void setUiContrastLocked(float contrast) {
- mUiContrast = contrast;
- }
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 61032dc..bea049c 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -849,7 +849,6 @@
}
}
-
/**
* Updates the last fill response when a view was entered.
*/
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 592045c..bcf50ad 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -356,6 +356,11 @@
public void onError() {
onErrorCallback.run();
}
+
+ @Override
+ public void onInflate() {
+ /* nothing */
+ }
});
if (inlineSuggestionsCallback.apply(inlineFillUi)) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 4a12e38..7f6ad43 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -276,6 +276,9 @@
@GuardedBy("mLock")
private DeathRecipient mClientVulture;
+ @GuardedBy("mLock")
+ private boolean mLoggedInlineDatasetShown;
+
/**
* Reference to the remote service.
*
@@ -739,6 +742,9 @@
return Collections.EMPTY_LIST;
}
final String typeHints = mService.getMaster().getPccProviderHints();
+ if (sVerbose) {
+ Slog.v(TAG, "TypeHints flag:" + typeHints);
+ }
if (TextUtils.isEmpty(typeHints)) {
return new ArrayList<>();
}
@@ -754,7 +760,7 @@
@GuardedBy("mLock")
void maybeRequestFieldClassificationFromServiceLocked() {
if (mClassificationState.mPendingFieldClassificationRequest == null) {
- Log.w(TAG, "Received AssistData without pending classification request");
+ Slog.w(TAG, "Received AssistData without pending classification request");
return;
}
@@ -788,7 +794,8 @@
final int requestId = receiverExtras.getInt(EXTRA_REQUEST_ID);
if (sVerbose) {
- Slog.v(TAG, "New structure for requestId " + requestId + ": " + structure);
+ Slog.v(TAG, "New structure for PCC Detection: requestId " + requestId + ": "
+ + structure);
}
synchronized (mLock) {
@@ -1122,6 +1129,13 @@
// structure is taken. This causes only one fill request per burst of focus changes.
cancelCurrentRequestLocked();
+ if (mClassificationState.mHintsToAutofillIdMap == null) {
+ if (sVerbose) {
+ Slog.v(TAG, "triggering field classification");
+ }
+ requestAssistStructureForPccLocked(flags | FLAG_PCC_DETECTION);
+ }
+
// Only ask IME to create inline suggestions request when
// 1. Autofill provider supports it or client enabled client suggestions.
// 2. The render service is available.
@@ -1373,7 +1387,6 @@
@Override
public void onFillRequestSuccess(int requestId, @Nullable FillResponse response,
@NonNull String servicePackageName, int requestFlags) {
-
final AutofillId[] fieldClassificationIds;
final LogMaker requestLog;
@@ -1606,10 +1619,68 @@
Set<Dataset> eligibleDatasets = new ArraySet<>();
Set<AutofillId> eligibleAutofillIds = new ArraySet<>();
for (Dataset dataset : response.getDatasets()) {
- if (dataset.getFieldIds() == null) continue;
+ if (dataset.getFieldIds() == null || dataset.getFieldIds().isEmpty()) continue;
if (dataset.getAutofillDatatypes() != null
- && dataset.getAutofillDatatypes().size() > 0) {
- continue;
+ && !dataset.getAutofillDatatypes().isEmpty()) {
+ // This dataset has information relevant for detection too, so we should filter
+ // them out. It's possible that some fields are applicable to hints only, as such,
+ // they need to be filtered off.
+ // TODO(b/266379948): Verify the logic and add tests
+ // Update dataset to only have non-null fieldValues
+
+ // Figure out if we need to process results.
+ boolean conversionRequired = false;
+ int newSize = dataset.getFieldIds().size();
+ for (AutofillId id : dataset.getFieldIds()) {
+ if (id == null) {
+ conversionRequired = true;
+ newSize--;
+ }
+ }
+
+ if (conversionRequired) {
+ ArrayList<AutofillId> fieldIds = new ArrayList<>(newSize);
+ ArrayList<AutofillValue> fieldValues = new ArrayList<>(newSize);
+ ArrayList<RemoteViews> fieldPresentations = new ArrayList<>(newSize);
+ ArrayList<RemoteViews> fieldDialogPresentations = new ArrayList<>(newSize);
+ ArrayList<InlinePresentation> fieldInlinePresentations =
+ new ArrayList<>(newSize);
+ ArrayList<InlinePresentation> fieldInlineTooltipPresentations =
+ new ArrayList<>(newSize);
+ ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>(newSize);
+
+ for (int i = 0; i < dataset.getFieldIds().size(); i++) {
+ AutofillId id = dataset.getFieldIds().get(i);
+ if (id != null) {
+ // Copy over
+ fieldIds.add(id);
+ fieldValues.add(dataset.getFieldValues().get(i));
+ fieldPresentations.add(dataset.getFieldPresentation(i));
+ fieldDialogPresentations.add(dataset.getFieldDialogPresentation(i));
+ fieldInlinePresentations.add(dataset.getFieldInlinePresentation(i));
+ fieldInlineTooltipPresentations.add(
+ dataset.getFieldInlineTooltipPresentation(i));
+ fieldFilters.add(dataset.getFilter(i));
+ }
+ }
+ dataset =
+ new Dataset(
+ fieldIds,
+ fieldValues,
+ fieldPresentations,
+ fieldDialogPresentations,
+ fieldInlinePresentations,
+ fieldInlineTooltipPresentations,
+ fieldFilters,
+ new ArrayList<>(),
+ dataset.getFieldContent(),
+ null,
+ null,
+ null,
+ null,
+ dataset.getId(),
+ dataset.getAuthentication());
+ }
}
eligibleDatasets.add(dataset);
for (AutofillId id : dataset.getFieldIds()) {
@@ -1636,6 +1707,7 @@
ArrayMap<String, Set<AutofillId>> hintsToAutofillIdMap =
mClassificationState.mHintsToAutofillIdMap;
+ // TODO(266379948): Handle group hints too.
ArrayMap<String, Set<AutofillId>> groupHintsToAutofillIdMap =
mClassificationState.mGroupHintsToAutofillIdMap;
@@ -1646,7 +1718,8 @@
for (int i = 0; i < datasets.size(); i++) {
Dataset dataset = datasets.get(i);
- if (dataset.getAutofillDatatypes() == null) continue;
+ if (dataset.getAutofillDatatypes() == null
+ || dataset.getAutofillDatatypes().isEmpty()) continue;
if (dataset.getFieldIds() != null && dataset.getFieldIds().size() > 0) continue;
ArrayList<AutofillId> fieldIds = new ArrayList<>();
@@ -1658,6 +1731,7 @@
ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>();
for (int j = 0; j < dataset.getAutofillDatatypes().size(); j++) {
+ if (dataset.getAutofillDatatypes().get(0) == null) continue;
String hint = dataset.getAutofillDatatypes().get(j);
if (hintsToAutofillIdMap.containsKey(hint)) {
@@ -1981,6 +2055,22 @@
Session::removeFromService, this));
}
+ // AutofillUiCallback
+ @Override
+ public void onShown(int uiType) {
+ synchronized (mLock) {
+ if (uiType == UI_TYPE_INLINE) {
+ if (mLoggedInlineDatasetShown) {
+ // Chip inflation already logged, do not log again.
+ // This is needed because every chip inflation will call this.
+ return;
+ }
+ mLoggedInlineDatasetShown = true;
+ }
+ mService.logDatasetShown(this.id, mClientState, uiType);
+ }
+ }
+
// AutoFillUiCallback
@Override
public void requestShowFillUi(AutofillId id, int width, int height,
@@ -3861,8 +3951,6 @@
synchronized (mLock) {
final ViewState currentView = mViewStates.get(mCurrentViewId);
currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
- mService.logDatasetShown(id, mClientState, UI_TYPE_DIALOG);
-
mPresentationStatsEventLogger.maybeSetCountShown(
response.getDatasets(), mCurrentViewId);
mPresentationStatsEventLogger.maybeSetDisplayPresentationType(UI_TYPE_DIALOG);
@@ -3892,10 +3980,6 @@
// back a response via callback.
final ViewState currentView = mViewStates.get(mCurrentViewId);
currentView.setState(ViewState.STATE_INLINE_SHOWN);
- // TODO(b/248378401): Fix it to log showed only when IME asks for inflation,
- // rather than here where framework sends back the response.
- mService.logDatasetShown(id, mClientState, UI_TYPE_INLINE);
-
// TODO(b/234475358): Log more accurate value of number of inline suggestions
// shown, inflated, and filtered.
mPresentationStatsEventLogger.maybeSetCountShown(
@@ -3912,7 +3996,6 @@
targetLabel, targetIcon, this, id, mCompatMode);
synchronized (mLock) {
- mService.logDatasetShown(id, mClientState, UI_TYPE_MENU);
mPresentationStatsEventLogger.maybeSetCountShown(
response.getDatasets(), mCurrentViewId);
mPresentationStatsEventLogger.maybeSetDisplayPresentationType(UI_TYPE_MENU);
@@ -4124,6 +4207,12 @@
return false;
}
+ // Set this to false - we are requesting a new inline request and haven't shown
+ // anything yet
+ synchronized (mLock) {
+ mLoggedInlineDatasetShown = false;
+ }
+
final InlineFillUi.InlineFillUiInfo inlineFillUiInfo =
new InlineFillUi.InlineFillUiInfo(request, focusedId,
filterText, remoteRenderService, userId, id);
@@ -4153,6 +4242,11 @@
InlineFillUi.emptyUi(focusedId));
}
}
+
+ @Override
+ public void onInflate() {
+ Session.this.onShown(UI_TYPE_INLINE);
+ }
});
return mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
}
@@ -4537,7 +4631,7 @@
if (mResponses == null) {
// Set initial capacity as 2 to handle cases where service always requires auth.
// TODO: add a metric for number of responses set by server, so we can use its average
- // as the initial array capacitiy.
+ // as the initial array capacity.
mResponses = new SparseArray<>(2);
}
mResponses.put(requestId, newResponse);
@@ -4959,6 +5053,7 @@
mClassificationGroupHintsMap = new ArrayMap<>();
mHintsToAutofillIdMap = new ArrayMap<>();
mGroupHintsToAutofillIdMap = new ArrayMap<>();
+ mClassificationCombinedHintsMap = new ArrayMap<>();
Set<android.service.assist.classification.FieldClassification> classifications =
response.getClassifications();
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 7db6e6f..8291610 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -98,6 +98,7 @@
void cancelSession();
void requestShowSoftInput(AutofillId id);
void requestFallbackFromFillDialog();
+ void onShown(int uiType);
}
public AutoFillUI(@NonNull Context context) {
@@ -237,6 +238,13 @@
}
@Override
+ public void onShown() {
+ if (mCallback != null) {
+ mCallback.onShown(UI_TYPE_MENU);
+ }
+ }
+
+ @Override
public void onDatasetPicked(Dataset dataset) {
log.setType(MetricsEvent.TYPE_ACTION);
hideFillUiUiThread(callback, true);
@@ -424,6 +432,11 @@
}
@Override
+ public void onShown() {
+ callback.onShown(UI_TYPE_DIALOG);
+ }
+
+ @Override
public void onDatasetPicked(Dataset dataset) {
log(MetricsEvent.TYPE_ACTION);
hideFillDialogUiThread(callback);
diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
index 72a38eb..dec0e76 100644
--- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -83,6 +83,7 @@
void onDatasetPicked(@NonNull Dataset dataset);
void onDismissed();
void onCanceled();
+ void onShown();
void startIntentSender(IntentSender intentSender);
}
@@ -148,7 +149,7 @@
mDialog.setContentView(decor);
setDialogParamsAsBottomSheet();
mDialog.setOnCancelListener((d) -> mCallback.onCanceled());
-
+ mDialog.setOnShowListener((d) -> mCallback.onShown());
show();
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 8fbdd81..76f4505 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -86,6 +86,7 @@
void onDatasetPicked(@NonNull Dataset dataset);
void onCanceled();
void onDestroy();
+ void onShown();
void requestShowFillUi(int width, int height,
IAutofillWindowPresenter windowPresenter);
void requestHideFillUi();
@@ -706,6 +707,7 @@
mWm.addView(mContentView, params);
mOverlayControl.hideOverlays();
mShowing = true;
+ mCallback.onShown();
} else {
mWm.updateViewLayout(mContentView, params);
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
index ff17590..ac8d962 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
@@ -334,6 +334,17 @@
* Callback on errors.
*/
void onError();
+
+ /**
+ * Callback when the when the IME inflates the suggestion
+ *
+ * This goes through the following path:
+ * 1. IME Chip inflation inflate() ->
+ * 2. RemoteInlineSuggestionUi::handleInlineSuggestionUiReady() ->
+ * 3. RemoteInlineSuggestionViewConnector::onRender() ->
+ * 4. InlineSuggestionUiCallback::onInflate()
+ */
+ void onInflate();
}
/**
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
index 9d4c9eb..52109ba 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
@@ -198,6 +198,11 @@
public void onError() {
Slog.w(TAG, "An error happened on the tooltip");
}
+
+ @Override
+ public void onInflate() {
+ /* nothing */
+ }
};
InlinePresentation tooltipInline = new InlinePresentation(tooltipPresentation.getSlice(),
diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionUi.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionUi.java
index 368f717..ddb60c6 100644
--- a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionUi.java
@@ -221,6 +221,7 @@
if (surfacePackage != null) {
surfacePackage.release();
}
+ mRemoteInlineSuggestionViewConnector.onRender();
}
private void handleOnClick() {
diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
index 46d435d..70443f9 100644
--- a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
+++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
@@ -54,6 +54,8 @@
@NonNull
private final Runnable mOnErrorCallback;
@NonNull
+ private final Runnable mOnInflateCallback;
+ @NonNull
private final Consumer<IntentSender> mStartIntentSenderFromClientApp;
RemoteInlineSuggestionViewConnector(
@@ -70,6 +72,7 @@
mOnAutofillCallback = onAutofillCallback;
mOnErrorCallback = uiCallback::onError;
+ mOnInflateCallback = uiCallback::onInflate;
mStartIntentSenderFromClientApp = uiCallback::startIntentSender;
}
@@ -103,6 +106,10 @@
mOnErrorCallback.run();
}
+ public void onRender() {
+ mOnInflateCallback.run();
+ }
+
/**
* Handles the callback for transferring the touch event on the remote view to the IME
* process.
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index 3814591..5a9c470 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -346,31 +346,31 @@
// Make sure do not schedule rebind for the case ServiceConnector still gets callback after
// app is uninstalled.
boolean stillAssociated = false;
+ // Make sure to clean up the state for all the associations
+ // that associate with this package.
+ boolean shouldScheduleRebind = false;
for (AssociationInfo ai :
mAssociationStore.getAssociationsForPackage(userId, packageName)) {
final int associationId = ai.getId();
stillAssociated = true;
-
if (ai.isSelfManaged()) {
// Do not rebind if primary one is died for selfManaged application.
if (isPrimary
&& mDevicePresenceMonitor.isDevicePresent(associationId)) {
mDevicePresenceMonitor.onSelfManagedDeviceReporterBinderDied(associationId);
- return false;
+ shouldScheduleRebind = false;
}
// Do not rebind if both primary and secondary services are died for
// selfManaged application.
- if (!isCompanionApplicationBound(userId, packageName)) {
- return false;
- }
+ shouldScheduleRebind = isCompanionApplicationBound(userId, packageName);
} else if (ai.isNotifyOnDeviceNearby()) {
// Always rebind for non-selfManaged devices.
- return true;
+ shouldScheduleRebind = true;
}
}
- return stillAssociated;
+ return stillAssociated && shouldScheduleRebind;
}
private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> {
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index d7a77cd..4010be9 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -232,11 +232,14 @@
}
final boolean alreadyPresent = isDevicePresent(newDeviceAssociationId);
- if (DEBUG && alreadyPresent) Log.i(TAG, "Device is already present.");
+ if (alreadyPresent) {
+ Log.i(TAG, "Device" + "id (" + newDeviceAssociationId + ") already present.");
+ }
final boolean added = presentDevicesForSource.add(newDeviceAssociationId);
- if (DEBUG && !added) {
- Log.w(TAG, "Association with id " + newDeviceAssociationId + " is ALREADY reported as "
+ if (!added) {
+ Log.w(TAG, "Association with id "
+ + newDeviceAssociationId + " is ALREADY reported as "
+ "present by this source (" + sourceLoggingTag + ")");
}
@@ -256,16 +259,17 @@
final boolean removed = presentDevicesForSource.remove(goneDeviceAssociationId);
if (!removed) {
- if (DEBUG) {
- Log.w(TAG, "Association with id " + goneDeviceAssociationId + " was NOT reported "
- + "as present by this source (" + sourceLoggingTag + ")");
- }
+ Log.w(TAG, "Association with id " + goneDeviceAssociationId + " was NOT reported "
+ + "as present by this source (" + sourceLoggingTag + ")");
+
return;
}
final boolean stillPresent = isDevicePresent(goneDeviceAssociationId);
if (stillPresent) {
- if (DEBUG) Log.i(TAG, " Device is still present.");
+ if (DEBUG) {
+ Log.i(TAG, " Device id (" + goneDeviceAssociationId + ") is still present.");
+ }
return;
}
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 5d46de3..7c32627 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -16,6 +16,7 @@
package com.android.server;
+import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
import static android.app.UiModeManager.MODE_NIGHT_AUTO;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
@@ -27,8 +28,11 @@
import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE;
import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
import static android.os.UserHandle.USER_SYSTEM;
+import static android.provider.Settings.Secure.CONTRAST_LEVEL;
import static android.util.TimeUtils.isTimeBetween;
+import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
+
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -38,6 +42,7 @@
import android.app.AlarmManager;
import android.app.IOnProjectionStateChangedListener;
import android.app.IUiModeManager;
+import android.app.IUiModeManagerCallback;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -71,6 +76,7 @@
import android.os.ShellCommand;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.provider.Settings;
import android.provider.Settings.Secure;
import android.service.dreams.Sandman;
import android.service.vr.IVrManager;
@@ -193,12 +199,19 @@
private PowerManagerInternal mLocalPowerManager;
@GuardedBy("mLock")
+ private final RemoteCallbackList<IUiModeManagerCallback> mUiModeManagerCallbacks =
+ new RemoteCallbackList<IUiModeManagerCallback>();
+
+ @GuardedBy("mLock")
@Nullable
private SparseArray<List<ProjectionHolder>> mProjectionHolders;
@GuardedBy("mLock")
@Nullable
private SparseArray<RemoteCallbackList<IOnProjectionStateChangedListener>> mProjectionListeners;
+ @GuardedBy("mLock")
+ private final SparseArray<Float> mContrasts = new SparseArray<>();
+
public UiModeManagerService(Context context) {
this(context, /* setupWizardComplete= */ false, /* tm= */ null, new Injector());
}
@@ -352,6 +365,19 @@
}
};
+ private final ContentObserver mContrastObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ synchronized (mLock) {
+ if (updateContrastLocked()) {
+ float contrast = getContrastLocked();
+ mUiModeManagerCallbacks.broadcast(ignoreRemoteException(callback ->
+ callback.notifyContrastChanged(contrast)));
+ }
+ }
+ }
+ };
+
private void updateSystemProperties() {
int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE,
mNightMode, 0);
@@ -407,6 +433,9 @@
context.getContentResolver()
.registerContentObserver(Secure.getUriFor(Secure.UI_NIGHT_MODE),
false, mDarkThemeObserver, 0);
+ context.getContentResolver().registerContentObserver(
+ Secure.getUriFor(Secure.CONTRAST_LEVEL), false,
+ mContrastObserver, UserHandle.USER_ALL);
context.registerReceiver(mDockModeReceiver,
new IntentFilter(Intent.ACTION_DOCK_EVENT));
IntentFilter batteryFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
@@ -634,6 +663,13 @@
private final IUiModeManager.Stub mService = new IUiModeManager.Stub() {
@Override
+ public void addCallback(IUiModeManagerCallback callback) {
+ synchronized (mLock) {
+ mUiModeManagerCallbacks.register(callback);
+ }
+ }
+
+ @Override
public void enableCarMode(@UiModeManager.EnableCarMode int flags,
@IntRange(from = 0) int priority, String callingPackage) {
if (isUiModeLocked()) {
@@ -1132,6 +1168,13 @@
}
}
}
+
+ @Override
+ public float getContrast() {
+ synchronized (mLock) {
+ return getContrastLocked();
+ }
+ }
};
private void enforceProjectionTypePermissions(@UiModeManager.ProjectionType int p) {
@@ -1214,6 +1257,30 @@
}
}
+ /**
+ * Return the contrast for the current user. If not cached, fetch it from the settings.
+ */
+ @GuardedBy("mLock")
+ private float getContrastLocked() {
+ if (!mContrasts.contains(mCurrentUser)) updateContrastLocked();
+ return mContrasts.get(mCurrentUser);
+ }
+
+ /**
+ * Read the contrast setting for the current user and update {@link #mContrasts}
+ * if the contrast changed. Returns true if {@link #mContrasts} was updated.
+ */
+ @GuardedBy("mLock")
+ private boolean updateContrastLocked() {
+ float contrast = Settings.Secure.getFloatForUser(getContext().getContentResolver(),
+ CONTRAST_LEVEL, CONTRAST_DEFAULT_VALUE, mCurrentUser);
+ if (Math.abs(mContrasts.get(mCurrentUser, Float.MAX_VALUE) - contrast) >= 1e-10) {
+ mContrasts.put(mCurrentUser, contrast);
+ return true;
+ }
+ return false;
+ }
+
private static class ProjectionHolder implements IBinder.DeathRecipient {
private final String mPackageName;
private final @UiModeManager.ProjectionType int mProjectionType;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4ba6854..37f744f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -99,6 +99,7 @@
import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
+import static android.view.Display.INVALID_DISPLAY;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__INTERNAL_NON_EXPORTED_COMPONENT_MATCH;
@@ -9681,8 +9682,8 @@
}
private void dumpEverything(FileDescriptor fd, PrintWriter pw, String[] args, int opti,
- boolean dumpAll, String dumpPackage, boolean dumpClient, boolean dumpNormalPriority,
- int dumpAppId, boolean dumpProxies) {
+ boolean dumpAll, String dumpPackage, int displayIdFilter, boolean dumpClient,
+ boolean dumpNormalPriority, int dumpAppId, boolean dumpProxies) {
ActiveServices.ServiceDumper sdumper;
@@ -9762,27 +9763,27 @@
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
}
- mAtmInternal.dump(
- DUMP_RECENTS_CMD, fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
+ mAtmInternal.dump(DUMP_RECENTS_CMD, fd, pw, args, opti, dumpAll, dumpClient,
+ dumpPackage, displayIdFilter);
pw.println();
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
}
- mAtmInternal.dump(
- DUMP_LASTANR_CMD, fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
+ mAtmInternal.dump(DUMP_LASTANR_CMD, fd, pw, args, opti, dumpAll, dumpClient,
+ dumpPackage, displayIdFilter);
pw.println();
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
}
- mAtmInternal.dump(
- DUMP_STARTER_CMD, fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
+ mAtmInternal.dump(DUMP_STARTER_CMD, fd, pw, args, opti, dumpAll, dumpClient,
+ dumpPackage, displayIdFilter);
if (dumpPackage == null) {
pw.println();
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
}
- mAtmInternal.dump(
- DUMP_CONTAINERS_CMD, fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
+ mAtmInternal.dump(DUMP_CONTAINERS_CMD, fd, pw, args, opti, dumpAll, dumpClient,
+ dumpPackage, displayIdFilter);
}
// Activities section is dumped as part of the Critical priority dump. Exclude the
// section if priority is Normal.
@@ -9791,8 +9792,8 @@
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
}
- mAtmInternal.dump(
- DUMP_ACTIVITIES_CMD, fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
+ mAtmInternal.dump(DUMP_ACTIVITIES_CMD, fd, pw, args, opti, dumpAll, dumpClient,
+ dumpPackage, displayIdFilter);
}
if (mAssociations.size() > 0) {
pw.println();
@@ -9865,6 +9866,8 @@
boolean dumpNormalPriority = false;
boolean dumpVisibleStacksOnly = false;
boolean dumpFocusedStackOnly = false;
+ boolean dumpVerbose = false;
+ int dumpDisplayId = INVALID_DISPLAY;
String dumpPackage = null;
int dumpUserId = UserHandle.USER_ALL;
@@ -9909,11 +9912,27 @@
pw.println("Error: --user option requires user id argument");
return;
}
+ } else if ("-d".equals(opt)) {
+ if (opti < args.length) {
+ dumpDisplayId = Integer.parseInt(args[opti]);
+ if (dumpDisplayId == INVALID_DISPLAY) {
+ pw.println("Error: -d cannot be used with INVALID_DISPLAY");
+ return;
+ }
+ opti++;
+ } else {
+ pw.println("Error: -d option requires display argument");
+ return;
+ }
+ dumpClient = true;
+ } else if ("--verbose".equals(opt)) {
+ dumpVerbose = true;
} else if ("-h".equals(opt)) {
ActivityManagerShellCommand.dumpHelp(pw, true);
return;
} else {
pw.println("Unknown argument: " + opt + "; use -h for help");
+ return;
}
}
@@ -10026,8 +10045,8 @@
|| DUMP_RECENTS_CMD.equals(cmd) || DUMP_RECENTS_SHORT_CMD.equals(cmd)
|| DUMP_TOP_RESUMED_ACTIVITY.equals(cmd)
|| DUMP_VISIBLE_ACTIVITIES.equals(cmd)) {
- mAtmInternal.dump(
- cmd, fd, pw, args, opti, true /* dumpAll */, dumpClient, dumpPackage);
+ mAtmInternal.dump(cmd, fd, pw, args, opti, /* dumpAll= */ true , dumpClient,
+ dumpPackage, dumpDisplayId);
} else if ("binder-proxies".equals(cmd)) {
if (opti >= args.length) {
dumpBinderProxies(pw, 0 /* minToDump */);
@@ -10194,7 +10213,8 @@
} else {
// Dumping a single activity?
if (!mAtmInternal.dumpActivity(fd, pw, cmd, args, opti, dumpAll,
- dumpVisibleStacksOnly, dumpFocusedStackOnly, dumpUserId)) {
+ dumpVisibleStacksOnly, dumpFocusedStackOnly, dumpVerbose, dumpDisplayId,
+ dumpUserId)) {
ActivityManagerShellCommand shell = new ActivityManagerShellCommand(this, true);
int res = shell.exec(this, null, fd, null, args, null,
new ResultReceiver(null));
@@ -10217,15 +10237,15 @@
if (dumpClient) {
// dumpEverything() will take the lock when needed, and momentarily drop
// it for dumping client state.
- dumpEverything(fd, pw, args, opti, dumpAll, dumpPackage, dumpClient,
- dumpNormalPriority, dumpAppId, true /* dumpProxies */);
+ dumpEverything(fd, pw, args, opti, dumpAll, dumpPackage, dumpDisplayId,
+ dumpClient, dumpNormalPriority, dumpAppId, /* dumpProxies= */ true);
} else {
// Take the lock here, so we get a consistent state for the entire dump;
// dumpEverything() will take the lock as well, which is fine for everything
// except dumping proxies, which can take a long time; exclude them.
synchronized(this) {
- dumpEverything(fd, pw, args, opti, dumpAll, dumpPackage, dumpClient,
- dumpNormalPriority, dumpAppId, false /* dumpProxies */);
+ dumpEverything(fd, pw, args, opti, dumpAll, dumpPackage, dumpDisplayId,
+ dumpClient, dumpNormalPriority, dumpAppId, /* dumpProxies= */ false);
}
}
if (dumpAll) {
@@ -14261,7 +14281,8 @@
// Apply permission policy around the use of specific broadcast options
void enforceBroadcastOptionPermissionsInternal(
@Nullable Bundle options, int callingUid) {
- enforceBroadcastOptionPermissionsInternal(BroadcastOptions.fromBundle(options), callingUid);
+ enforceBroadcastOptionPermissionsInternal(BroadcastOptions.fromBundleNullable(options),
+ callingUid);
}
void enforceBroadcastOptionPermissionsInternal(
@@ -14277,7 +14298,7 @@
}
if (options.isInteractive()) {
enforceCallingPermission(
- android.Manifest.permission.COMPONENT_OPTION_INTERACTIVE,
+ android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE,
"setInteractive");
}
}
@@ -14314,9 +14335,9 @@
final int res = broadcastIntentLockedTraced(callerApp, callerPackage, callerFeatureId,
intent, resolvedType, resultToApp, resultTo, resultCode, resultData, resultExtras,
requiredPermissions, excludedPermissions, excludedPackages, appOp,
- BroadcastOptions.fromBundle(bOptions), ordered, sticky, callingPid, callingUid,
- realCallingUid, realCallingPid, userId, backgroundStartPrivileges,
- broadcastAllowList, filterExtrasForReceiver);
+ BroadcastOptions.fromBundleNullable(bOptions), ordered, sticky,
+ callingPid, callingUid, realCallingUid, realCallingPid, userId,
+ backgroundStartPrivileges, broadcastAllowList, filterExtrasForReceiver);
BroadcastQueue.traceEnd(cookie);
return res;
}
@@ -18118,8 +18139,12 @@
| Intent.FLAG_RECEIVER_REPLACE_PENDING
| Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
+ final Bundle configChangedOptions = new BroadcastOptions()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .setDeferUntilActive(true)
+ .toBundle();
broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
- null, null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+ null, null, OP_NONE, configChangedOptions, false, false, MY_PID, SYSTEM_UID,
Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL);
if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) {
intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
@@ -18178,8 +18203,14 @@
intent.putExtra("reason", reason);
}
+ final BroadcastOptions options = new BroadcastOptions()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .setDeferUntilActive(true);
+ if (reason != null) {
+ options.setDeliveryGroupMatchingKey(Intent.ACTION_CLOSE_SYSTEM_DIALOGS, reason);
+ }
broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
- null, null, OP_NONE, null, false, false, -1, SYSTEM_UID,
+ null, null, OP_NONE, options.toBundle(), false, false, -1, SYSTEM_UID,
Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 523ed69..01bb549 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -3945,6 +3945,7 @@
pw.println(" package [PACKAGE_NAME]: all state related to given package");
pw.println(" all: dump all activities");
pw.println(" top: dump the top activity");
+ pw.println(" users: user state");
pw.println(" WHAT may also be a COMP_SPEC to dump activities.");
pw.println(" COMP_SPEC may be a component name (com.foo/.myApp),");
pw.println(" a partial substring in a component name, a");
@@ -3952,9 +3953,11 @@
pw.println(" -a: include all available server state.");
pw.println(" -c: include client state.");
pw.println(" -p: limit output to given package.");
+ pw.println(" -d: limit output to given display.");
pw.println(" --checkin: output checkin format, resetting data.");
pw.println(" --C: output checkin format, not resetting data.");
pw.println(" --proto: output dump in protocol buffer format.");
+ pw.println(" --verbose: dumps extra information.");
pw.printf(" %s: dump just the DUMPABLE-related state of an activity. Use the %s "
+ "option to list the supported DUMPABLEs\n", Activity.DUMP_ARG_DUMP_DUMPABLE,
Activity.DUMP_ARG_LIST_DUMPABLES);
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index ac2c725..dd6598f 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -20,6 +20,7 @@
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
+import static android.view.Display.INVALID_DISPLAY;
import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW;
@@ -1604,8 +1605,8 @@
mService.mServices.newServiceDumperLocked(null, catPw, emptyArgs, 0,
false, null).dumpLocked();
catPw.println();
- mService.mAtmInternal.dump(
- DUMP_ACTIVITIES_CMD, null, catPw, emptyArgs, 0, false, false, null);
+ mService.mAtmInternal.dump(DUMP_ACTIVITIES_CMD, null, catPw, emptyArgs, 0, false, false,
+ null, INVALID_DISPLAY);
catPw.flush();
}
dropBuilder.append(catSw.toString());
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 0ee883f..f236a96 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -402,6 +402,7 @@
public void systemServicesReady() {
mStats.systemServicesReady(mContext);
+ mCpuWakeupStats.systemServicesReady();
mWorker.systemServicesReady();
final INetworkManagementService nms = INetworkManagementService.Stub.asInterface(
ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 2e3e635..ddc9e91 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -96,6 +96,10 @@
sGlobalSettingToTypeMap.put(
Settings.Global.ANGLE_EGL_FEATURES, String.class);
sGlobalSettingToTypeMap.put(
+ Settings.Global.ANGLE_DEFERLIST, String.class);
+ sGlobalSettingToTypeMap.put(
+ Settings.Global.ANGLE_DEFERLIST_MODE, String.class);
+ sGlobalSettingToTypeMap.put(
Settings.Global.SHOW_ANGLE_IN_USE_DIALOG_BOX, String.class);
sGlobalSettingToTypeMap.put(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, int.class);
sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_APP, String.class);
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index bb8d3f4..23a384f 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -67,8 +67,8 @@
private SparseIntArray mPendingUidStates = new SparseIntArray();
private SparseIntArray mCapability = new SparseIntArray();
private SparseIntArray mPendingCapability = new SparseIntArray();
- private SparseBooleanArray mVisibleAppWidget = new SparseBooleanArray();
- private SparseBooleanArray mPendingVisibleAppWidget = new SparseBooleanArray();
+ private SparseBooleanArray mAppWidgetVisible = new SparseBooleanArray();
+ private SparseBooleanArray mPendingAppWidgetVisible = new SparseBooleanArray();
private SparseLongArray mPendingCommitTime = new SparseLongArray();
private SparseBooleanArray mPendingGone = new SparseBooleanArray();
@@ -140,7 +140,7 @@
private int evalModeInternal(int uid, int code, int uidState, int uidCapability) {
- if (getUidVisibleAppWidget(uid) || mActivityManagerInternal.isPendingTopUid(uid)
+ if (getUidAppWidgetVisible(uid) || mActivityManagerInternal.isPendingTopUid(uid)
|| mActivityManagerInternal.isTempAllowlistedForFgsWhileInUse(uid)) {
return MODE_ALLOWED;
}
@@ -205,7 +205,7 @@
int numUids = uidPackageNames.size();
for (int i = 0; i < numUids; i++) {
int uid = uidPackageNames.keyAt(i);
- mPendingVisibleAppWidget.put(uid, visible);
+ mPendingAppWidgetVisible.put(uid, visible);
commitUidPendingState(uid);
}
@@ -291,9 +291,9 @@
ActivityManager.printCapabilitiesFull(pw, pendingCapability);
pw.println();
}
- boolean appWidgetVisible = mVisibleAppWidget.get(uid, false);
+ boolean appWidgetVisible = mAppWidgetVisible.get(uid, false);
// if no pendingAppWidgetVisible set to appWidgetVisible to suppress output
- boolean pendingAppWidgetVisible = mPendingVisibleAppWidget.get(uid, appWidgetVisible);
+ boolean pendingAppWidgetVisible = mPendingAppWidgetVisible.get(uid, appWidgetVisible);
pw.print(" appWidgetVisible=");
pw.println(appWidgetVisible);
if (appWidgetVisible != pendingAppWidgetVisible) {
@@ -333,25 +333,25 @@
mUidStates.get(uid, MIN_PRIORITY_UID_STATE));
int pendingCapability = mPendingCapability.get(uid,
mCapability.get(uid, PROCESS_CAPABILITY_NONE));
- boolean pendingVisibleAppWidget = mPendingVisibleAppWidget.get(uid,
- mVisibleAppWidget.get(uid, false));
+ boolean pendingAppWidgetVisible = mPendingAppWidgetVisible.get(uid,
+ mAppWidgetVisible.get(uid, false));
int uidState = mUidStates.get(uid, MIN_PRIORITY_UID_STATE);
int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE);
- boolean visibleAppWidget = mVisibleAppWidget.get(uid, false);
+ boolean appWidgetVisible = mAppWidgetVisible.get(uid, false);
if (uidState != pendingUidState
|| capability != pendingCapability
- || visibleAppWidget != pendingVisibleAppWidget) {
+ || appWidgetVisible != pendingAppWidgetVisible) {
boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
!= pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
|| capability != pendingCapability
- || visibleAppWidget != pendingVisibleAppWidget;
+ || appWidgetVisible != pendingAppWidgetVisible;
if (foregroundChange) {
// To save on memory usage, log only interesting changes.
mEventLog.logCommitUidState(uid, pendingUidState, pendingCapability,
- pendingVisibleAppWidget);
+ pendingAppWidgetVisible, appWidgetVisible != pendingAppWidgetVisible);
}
for (int i = 0; i < mUidStateChangedCallbacks.size(); i++) {
@@ -367,17 +367,17 @@
if (mPendingGone.get(uid, false)) {
mUidStates.delete(uid);
mCapability.delete(uid);
- mVisibleAppWidget.delete(uid);
+ mAppWidgetVisible.delete(uid);
mPendingGone.delete(uid);
} else {
mUidStates.put(uid, pendingUidState);
mCapability.put(uid, pendingCapability);
- mVisibleAppWidget.put(uid, pendingVisibleAppWidget);
+ mAppWidgetVisible.put(uid, pendingAppWidgetVisible);
}
mPendingUidStates.delete(uid);
mPendingCapability.delete(uid);
- mPendingVisibleAppWidget.delete(uid);
+ mPendingAppWidgetVisible.delete(uid);
mPendingCommitTime.delete(uid);
}
@@ -385,8 +385,8 @@
return mCapability.get(uid, ActivityManager.PROCESS_CAPABILITY_NONE);
}
- private boolean getUidVisibleAppWidget(int uid) {
- return mVisibleAppWidget.get(uid, false);
+ private boolean getUidAppWidgetVisible(int uid) {
+ return mAppWidgetVisible.get(uid, false);
}
private static class EventLog {
@@ -398,6 +398,9 @@
// Memory usage: 24 * size bytes
private static final int EVAL_FOREGROUND_MODE_MAX_SIZE = 200;
+ private static final int APP_WIDGET_VISIBLE = 1 << 0;
+ private static final int APP_WIDGET_VISIBLE_CHANGED = 1 << 1;
+
private final DelayableExecutor mExecutor;
private final Thread mExecutorThread;
@@ -446,16 +449,18 @@
mUpdateUidProcStateLogTimestamps[idx] = timestamp;
}
- void logCommitUidState(int uid, int uidState, int capability, boolean visible) {
+ void logCommitUidState(int uid, int uidState, int capability, boolean appWidgetVisible,
+ boolean appWidgetVisibleChanged) {
if (COMMIT_UID_STATE_LOG_MAX_SIZE == 0) {
return;
}
mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logCommitUidStateAsync,
- this, System.currentTimeMillis(), uid, uidState, capability, visible));
+ this, System.currentTimeMillis(), uid, uidState, capability, appWidgetVisible,
+ appWidgetVisibleChanged));
}
void logCommitUidStateAsync(long timestamp, int uid, int uidState, int capability,
- boolean visible) {
+ boolean appWidgetVisible, boolean appWidgetVisibleChanged) {
int idx = (mCommitUidStateLogHead + mCommitUidStateLogSize)
% COMMIT_UID_STATE_LOG_MAX_SIZE;
if (mCommitUidStateLogSize == COMMIT_UID_STATE_LOG_MAX_SIZE) {
@@ -468,7 +473,13 @@
mCommitUidStateLog[idx][0] = uid;
mCommitUidStateLog[idx][1] = uidState;
mCommitUidStateLog[idx][2] = capability;
- mCommitUidStateLog[idx][3] = visible ? 1 : 0;
+ mCommitUidStateLog[idx][3] = 0;
+ if (appWidgetVisible) {
+ mCommitUidStateLog[idx][3] += APP_WIDGET_VISIBLE;
+ }
+ if (appWidgetVisibleChanged) {
+ mCommitUidStateLog[idx][3] += APP_WIDGET_VISIBLE_CHANGED;
+ }
mCommitUidStateLogTimestamps[idx] = timestamp;
}
@@ -570,7 +581,9 @@
int uid = mCommitUidStateLog[idx][0];
int uidState = mCommitUidStateLog[idx][1];
int capability = mCommitUidStateLog[idx][2];
- boolean visibleAppWidget = mCommitUidStateLog[idx][3] != 0;
+ boolean appWidgetVisible = (mCommitUidStateLog[idx][3] & APP_WIDGET_VISIBLE) != 0;
+ boolean appWidgetVisibleChanged =
+ (mCommitUidStateLog[idx][3] & APP_WIDGET_VISIBLE_CHANGED) != 0;
TimeUtils.dumpTime(pw, timestamp);
@@ -585,8 +598,12 @@
pw.print(" capability=");
pw.print(ActivityManager.getCapabilitiesSummary(capability) + " ");
- pw.print(" visibleAppWidget=");
- pw.print(visibleAppWidget);
+ pw.print(" appWidgetVisible=");
+ pw.print(appWidgetVisible);
+
+ if (appWidgetVisibleChanged) {
+ pw.print(" (changed)");
+ }
pw.println();
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0d0e576..c50e275 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -8588,7 +8588,9 @@
if (isMutable()) {
// For call stream, align mute only when muted, not when index is set to 0
mVolumeGroupState.mute(
- forceMuteState ? mIsMuted : groupIndex == 0 || mIsMuted);
+ forceMuteState ? mIsMuted :
+ (groupIndex == 0 && !isCallStream(mStreamType))
+ || mIsMuted);
}
}
}
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index cf81dbe..6a97243 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -366,6 +366,21 @@
return;
}
+ SoundDoseRecord[] doseRecordsArray;
+ synchronized (mCsdStateLock) {
+ mCurrentCsd = csd;
+
+ mDoseRecords.clear();
+
+ if (mCurrentCsd > 0.0f) {
+ final SoundDoseRecord record = new SoundDoseRecord();
+ record.timestamp = SystemClock.elapsedRealtime();
+ record.value = csd;
+ mDoseRecords.add(record);
+ }
+ doseRecordsArray = mDoseRecords.toArray(new SoundDoseRecord[0]);
+ }
+
final ISoundDose soundDose = mSoundDose.get();
if (soundDose == null) {
Log.w(TAG, "Sound dose interface not initialized");
@@ -373,11 +388,7 @@
}
try {
- final SoundDoseRecord record = new SoundDoseRecord();
- record.timestamp = System.currentTimeMillis();
- record.value = csd;
- final SoundDoseRecord[] recordArray = new SoundDoseRecord[] { record };
- soundDose.resetCsd(csd, recordArray);
+ soundDose.resetCsd(csd, doseRecordsArray);
} catch (RemoteException e) {
Log.e(TAG, "Exception while setting the CSD value", e);
}
@@ -648,6 +659,11 @@
/*package*/ void dump(PrintWriter pw) {
pw.print(" mEnableCsd="); pw.println(mEnableCsd);
+ if (mEnableCsd) {
+ synchronized (mCsdStateLock) {
+ pw.print(" mCurrentCsd="); pw.println(mCurrentCsd);
+ }
+ }
pw.print(" mSafeMediaVolumeState=");
pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState));
pw.print(" mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex);
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 592daa6..720ea99 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -50,7 +50,12 @@
import java.io.PrintWriter;
-class AutomaticBrightnessController {
+/**
+ * Manages the associated display brightness when in auto-brightness mode. This is also
+ * responsible for managing the brightness lux-nits mapping strategies. Internally also listens to
+ * the LightSensor and adjusts the system brightness in case of changes in the surrounding lux.
+ */
+public class AutomaticBrightnessController {
private static final String TAG = "AutomaticBrightnessController";
private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false;
@@ -1140,7 +1145,7 @@
if (mCurrentBrightnessMapper != null) {
return mCurrentBrightnessMapper.convertToFloatScale(nits);
} else {
- return -1.0f;
+ return PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
}
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index d7c1529..d047183 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -322,9 +322,10 @@
public abstract float convertToNits(float brightness);
/**
- * Converts the provided nits value to a float value if possible.
+ * Converts the provided nit value to a float scale value if possible.
*
- * Returns -1.0f if there's no available mapping for the nits to float.
+ * Returns {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} if there's no available mapping for
+ * the nits to float scale.
*/
public abstract float convertToFloatScale(float nits);
@@ -679,7 +680,7 @@
@Override
public float convertToFloatScale(float nits) {
- return -1.0f;
+ return PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
@Override
diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java
index 4a9b562..de42370 100644
--- a/services/core/java/com/android/server/display/BrightnessSetting.java
+++ b/services/core/java/com/android/server/display/BrightnessSetting.java
@@ -121,6 +121,23 @@
}
}
+ /**
+ * @return The brightness for the default display in nits. Used when the underlying display
+ * device has changed but we want to persist the nit value.
+ */
+ public float getBrightnessNitsForDefaultDisplay() {
+ return mPersistentDataStore.getBrightnessNitsForDefaultDisplay();
+ }
+
+ /**
+ * Set brightness in nits for the default display. Used when we want to persist the nit value
+ * even if the underlying display device changes.
+ * @param nits The brightness value in nits
+ */
+ public void setBrightnessNitsForDefaultDisplay(float nits) {
+ mPersistentDataStore.setBrightnessNitsForDefaultDisplay(nits);
+ }
+
private void notifyListeners(float brightness) {
for (BrightnessSettingListener l : mListeners) {
l.onBrightnessChanged(brightness);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 55d2921..ea157c8 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -128,6 +128,7 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.Spline;
+import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.DisplayEventReceiver;
import android.view.DisplayInfo;
@@ -250,6 +251,7 @@
private ActivityManagerInternal mActivityManagerInternal;
private ActivityManager mActivityManager;
private UidImportanceListener mUidImportanceListener = new UidImportanceListener();
+ @Nullable
private IMediaProjectionManager mProjectionService;
private DeviceStateManagerInternal mDeviceStateManager;
@GuardedBy("mSyncRoot")
@@ -1494,8 +1496,9 @@
final long token = Binder.clearCallingIdentity();
try {
+ final int displayId;
synchronized (mSyncRoot) {
- final int displayId =
+ displayId =
createVirtualDisplayLocked(
callback,
projection,
@@ -1509,8 +1512,39 @@
mDisplayWindowPolicyControllers.put(
displayId, Pair.create(virtualDevice, dwpc));
}
- return displayId;
}
+
+ // When calling setContentRecordingSession into the WindowManagerService, the WMS
+ // attempts to acquire a lock before executing its main body. Due to this, we need
+ // to be sure that it isn't called while the DisplayManagerService is also holding
+ // a lock, to avoid a deadlock scenario.
+ final ContentRecordingSession session =
+ virtualDisplayConfig.getContentRecordingSession();
+
+ if (displayId != Display.INVALID_DISPLAY && session != null) {
+ // Only attempt to set content recording session if there are details to set and a
+ // VirtualDisplay has been successfully constructed.
+ session.setDisplayId(displayId);
+
+ // We set the content recording session here on the server side instead of using
+ // a second AIDL call in MediaProjection. By ensuring that a virtual display has
+ // been constructed before calling setContentRecordingSession, we avoid a race
+ // condition between the DMS & WMS which could lead to the MediaProjection
+ // being pre-emptively torn down.
+ if (!mWindowManagerInternal.setContentRecordingSession(session)) {
+ // Unable to start mirroring, so tear down projection & release VirtualDisplay.
+ try {
+ getProjectionService().stopActiveProjection();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to tell MediaProjectionManagerService to stop the "
+ + "active projection", e);
+ }
+ releaseVirtualDisplayInternal(callback.asBinder());
+ return Display.INVALID_DISPLAY;
+ }
+ }
+
+ return displayId;
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -2804,8 +2838,7 @@
private IMediaProjectionManager getProjectionService() {
if (mProjectionService == null) {
- IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE);
- mProjectionService = IMediaProjectionManager.Stub.asInterface(b);
+ mProjectionService = mInjector.getProjectionService();
}
return mProjectionService;
}
@@ -2964,6 +2997,11 @@
boolean getHdrOutputConversionSupport() {
return DisplayControl.getHdrOutputConversionSupport();
}
+
+ IMediaProjectionManager getProjectionService() {
+ IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE);
+ return IMediaProjectionManager.Stub.asInterface(b);
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 84fe8f2..055ca37 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -233,6 +233,10 @@
// True if should use light sensor to automatically determine doze screen brightness.
private final boolean mAllowAutoBrightnessWhileDozingConfig;
+ // True if we want to persist the brightness value in nits even if the underlying display
+ // device changes.
+ private final boolean mPersistBrightnessNitsForDefaultDisplay;
+
// True if the brightness config has changed and the short-term model needs to be reset
private boolean mShouldResetShortTermModel;
@@ -590,6 +594,9 @@
mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing);
+ mPersistBrightnessNitsForDefaultDisplay = resources.getBoolean(
+ com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay);
+
mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked()
.getDisplayDeviceConfig();
@@ -658,7 +665,7 @@
loadProximitySensor();
- mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
+ loadNitBasedBrightnessSetting();
mBrightnessToFollow = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
@@ -745,10 +752,10 @@
@Override
public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) {
mHbmController.onAmbientLuxChange(ambientLux);
- if (mAutomaticBrightnessController == null || nits < 0) {
+ if (nits < 0) {
mBrightnessToFollow = leadDisplayBrightness;
} else {
- float brightness = mAutomaticBrightnessController.convertToFloatScale(nits);
+ float brightness = convertToFloatScale(nits);
if (isValidBrightnessValue(brightness)) {
mBrightnessToFollow = brightness;
} else {
@@ -895,6 +902,7 @@
mDisplayDeviceConfig = config;
mBrightnessThrottlingDataId = brightnessThrottlingDataId;
loadFromDisplayDeviceConfig(token, info, hbmMetadata);
+ loadNitBasedBrightnessSetting();
/// Since the underlying display-device changed, we really don't know the
// last command that was sent to change it's state. Lets assume it is unknown so
@@ -2563,11 +2571,34 @@
return clampAbsoluteBrightness(brightness);
}
+ private void loadNitBasedBrightnessSetting() {
+ if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
+ float brightnessNitsForDefaultDisplay =
+ mBrightnessSetting.getBrightnessNitsForDefaultDisplay();
+ if (brightnessNitsForDefaultDisplay >= 0) {
+ float brightnessForDefaultDisplay = convertToFloatScale(
+ brightnessNitsForDefaultDisplay);
+ if (isValidBrightnessValue(brightnessForDefaultDisplay)) {
+ mBrightnessSetting.setBrightness(brightnessForDefaultDisplay);
+ mCurrentScreenBrightnessSetting = brightnessForDefaultDisplay;
+ return;
+ }
+ }
+ }
+ mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
+ }
+
@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);
+ if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
+ float nits = convertToNits(brightnessValue);
+ if (nits >= 0) {
+ mBrightnessSetting.setBrightnessNitsForDefaultDisplay(nits);
+ }
+ }
}
@Override
@@ -2582,7 +2613,7 @@
return;
}
setCurrentScreenBrightness(brightnessValue);
- mBrightnessSetting.setBrightness(brightnessValue);
+ setBrightness(brightnessValue);
}
private void setCurrentScreenBrightness(float brightnessValue) {
@@ -2661,6 +2692,13 @@
return mAutomaticBrightnessController.convertToNits(brightness);
}
+ private float convertToFloatScale(float nits) {
+ if (mAutomaticBrightnessController == null) {
+ return PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ }
+ return mAutomaticBrightnessController.convertToFloatScale(nits);
+ }
+
@GuardedBy("mLock")
private void updatePendingProximityRequestsLocked() {
mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
@@ -2759,6 +2797,8 @@
pw.println(" mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
pw.println(" mAllowAutoBrightnessWhileDozingConfig="
+ mAllowAutoBrightnessWhileDozingConfig);
+ pw.println(" mPersistBrightnessNitsForDefaultDisplay="
+ + mPersistBrightnessNitsForDefaultDisplay);
pw.println(" mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
pw.println(" mColorFadeFadesConfig=" + mColorFadeFadesConfig);
pw.println(" mColorFadeEnabled=" + mColorFadeEnabled);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 297a6f8..3c937e8 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -861,7 +861,8 @@
noteScreenBrightness(mPowerState.getScreenBrightness());
// Initialize all of the brightness tracking state
- final float brightness = convertToNits(mPowerState.getScreenBrightness());
+ final float brightness = mDisplayBrightnessController.convertToNits(
+ mPowerState.getScreenBrightness());
if (mBrightnessTracker != null && brightness >= PowerManager.BRIGHTNESS_MIN) {
mBrightnessTracker.start(brightness);
}
@@ -1024,6 +1025,8 @@
mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
mDisplayDeviceConfig.getAmbientHorizonShort(),
mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userBrightness);
+ mDisplayBrightnessController.setAutomaticBrightnessController(
+ mAutomaticBrightnessController);
mBrightnessEventRingBuffer =
new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
@@ -1389,7 +1392,8 @@
: mAutomaticBrightnessController.getAmbientLux();
for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
- follower.setBrightnessToFollow(rawBrightnessState, convertToNits(rawBrightnessState),
+ follower.setBrightnessToFollow(rawBrightnessState,
+ mDisplayBrightnessController.convertToNits(rawBrightnessState),
ambientLux);
}
@@ -2191,10 +2195,10 @@
@Override
public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) {
mHbmController.onAmbientLuxChange(ambientLux);
- if (mAutomaticBrightnessController == null || nits < 0) {
+ if (nits < 0) {
mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness);
} else {
- float brightness = mAutomaticBrightnessController.convertToFloatScale(nits);
+ float brightness = mDisplayBrightnessController.convertToFloatScale(nits);
if (BrightnessUtils.isValidBrightnessValue(brightness)) {
mDisplayBrightnessController.setBrightnessToFollow(brightness);
} else {
@@ -2230,7 +2234,7 @@
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
boolean wasShortTermModelActive) {
- final float brightnessInNits = convertToNits(brightness);
+ final float brightnessInNits = mDisplayBrightnessController.convertToNits(brightness);
if (mUseAutoBrightness && brightnessInNits >= 0.0f
&& mAutomaticBrightnessController != null && mBrightnessTracker != null) {
// We only want to track changes on devices that can actually map the display backlight
@@ -2247,13 +2251,6 @@
}
}
- private float convertToNits(float brightness) {
- if (mAutomaticBrightnessController == null) {
- return -1f;
- }
- return mAutomaticBrightnessController.convertToNits(brightness);
- }
-
@Override
public void addDisplayBrightnessFollower(DisplayPowerControllerInterface follower) {
synchronized (mLock) {
@@ -2513,17 +2510,17 @@
int appliedRbcStrength = event.isRbcEnabled() ? event.getRbcStrength() : -1;
float appliedHbmMaxNits =
event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
- ? -1f : convertToNits(event.getHbmMax());
+ ? -1f : mDisplayBrightnessController.convertToNits(event.getHbmMax());
// thermalCapNits set to -1 if not currently capping max brightness
float appliedThermalCapNits =
event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
- ? -1f : convertToNits(event.getThermalMax());
+ ? -1f : mDisplayBrightnessController.convertToNits(event.getThermalMax());
if (mLogicalDisplay.getPrimaryDisplayDeviceLocked() != null
&& mLogicalDisplay.getPrimaryDisplayDeviceLocked()
.getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL) {
FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
- convertToNits(event.getInitialBrightness()),
- convertToNits(event.getBrightness()),
+ mDisplayBrightnessController.convertToNits(event.getInitialBrightness()),
+ mDisplayBrightnessController.convertToNits(event.getBrightness()),
event.getLux(),
event.getPhysicalDisplayId(),
event.wasShortTermModelActive(),
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index e7601bc..ec70c89 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -94,6 +94,7 @@
* </brightness-curve>
* </brightness-configuration>
* </brightness-configurations>
+ * <brightness-nits-for-default-display>600</brightness-nits-for-default-display>
* </display-manager-state>
* </code>
*
@@ -130,6 +131,9 @@
private static final String TAG_RESOLUTION_HEIGHT = "resolution-height";
private static final String TAG_REFRESH_RATE = "refresh-rate";
+ private static final String TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY =
+ "brightness-nits-for-default-display";
+
// Remembered Wifi display devices.
private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
@@ -137,6 +141,8 @@
private final HashMap<String, DisplayState> mDisplayStates =
new HashMap<String, DisplayState>();
+ private float mBrightnessNitsForDefaultDisplay = -1;
+
// Display values which should be stable across the device's lifetime.
private final StableDeviceValues mStableDeviceValues = new StableDeviceValues();
@@ -312,6 +318,19 @@
return false;
}
+ public float getBrightnessNitsForDefaultDisplay() {
+ return mBrightnessNitsForDefaultDisplay;
+ }
+
+ public boolean setBrightnessNitsForDefaultDisplay(float nits) {
+ if (nits != mBrightnessNitsForDefaultDisplay) {
+ mBrightnessNitsForDefaultDisplay = nits;
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
public boolean setUserPreferredRefreshRate(DisplayDevice displayDevice, float refreshRate) {
final String displayDeviceUniqueId = displayDevice.getUniqueId();
if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) {
@@ -513,6 +532,10 @@
if (parser.getName().equals(TAG_BRIGHTNESS_CONFIGURATIONS)) {
mGlobalBrightnessConfigurations.loadFromXml(parser);
}
+ if (parser.getName().equals(TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY)) {
+ String value = parser.nextText();
+ mBrightnessNitsForDefaultDisplay = Float.parseFloat(value);
+ }
}
}
@@ -592,6 +615,9 @@
serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
mGlobalBrightnessConfigurations.saveToXml(serializer);
serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
+ serializer.startTag(null, TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY);
+ serializer.text(Float.toString(mBrightnessNitsForDefaultDisplay));
+ serializer.endTag(null, TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY);
serializer.endTag(null, TAG_DISPLAY_MANAGER_STATE);
serializer.endDocument();
}
@@ -615,6 +641,7 @@
mStableDeviceValues.dump(pw, " ");
pw.println(" GlobalBrightnessConfigurations:");
mGlobalBrightnessConfigurations.dump(pw, " ");
+ pw.println(" mBrightnessNitsForDefaultDisplay=" + mBrightnessNitsForDefaultDisplay);
}
private static final class DisplayState {
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index eda15ae..4f7a2ba 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -88,7 +88,17 @@
// Called with SyncRoot lock held.
public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
Context context, Handler handler, Listener listener) {
- this(syncRoot, context, handler, listener, DisplayControl::createDisplay);
+ this(syncRoot, context, handler, listener, new SurfaceControlDisplayFactory() {
+ @Override
+ public IBinder createDisplay(String name, boolean secure, float requestedRefreshRate) {
+ return DisplayControl.createDisplay(name, secure, requestedRefreshRate);
+ }
+
+ @Override
+ public void destroyDisplay(IBinder displayToken) {
+ DisplayControl.destroyDisplay(displayToken);
+ }
+ });
}
@VisibleForTesting
@@ -311,7 +321,7 @@
mSurface.release();
mSurface = null;
}
- DisplayControl.destroyDisplay(getDisplayTokenLocked());
+ mSurfaceControlDisplayFactory.destroyDisplay(getDisplayTokenLocked());
if (mProjection != null && mMediaProjectionCallback != null) {
try {
mProjection.unregisterCallback(mMediaProjectionCallback);
@@ -653,5 +663,12 @@
* @return The token reference for the display in SurfaceFlinger.
*/
IBinder createDisplay(String name, boolean secure, float requestedRefreshRate);
+
+ /**
+ * Destroy a display in SurfaceFlinger.
+ *
+ * @param displayToken The display token for the display to be destroyed.
+ */
+ void destroyDisplay(IBinder displayToken);
}
}
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 68758ca..2916fef 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -16,14 +16,17 @@
package com.android.server.display.brightness;
+import android.annotation.Nullable;
import android.content.Context;
import android.hardware.display.DisplayManagerInternal;
import android.os.HandlerExecutor;
import android.os.PowerManager;
import android.util.IndentingPrintWriter;
+import android.view.Display;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.AutomaticBrightnessController;
import com.android.server.display.BrightnessSetting;
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
@@ -84,6 +87,15 @@
// callback is not executed in sync and is not blocking the thread from which it is called.
private final HandlerExecutor mBrightnessChangeExecutor;
+ // True if we want to persist the brightness value in nits even if the underlying display
+ // device changes.
+ private final boolean mPersistBrightnessNitsForDefaultDisplay;
+
+ // The controller for the automatic brightness level.
+ // TODO(b/265415257): Move to the automatic brightness strategy
+ @Nullable
+ private AutomaticBrightnessController mAutomaticBrightnessController;
+
/**
* The constructor of DisplayBrightnessController.
*/
@@ -103,6 +115,8 @@
mDisplayBrightnessStrategySelector = injector.getDisplayBrightnessStrategySelector(context,
displayId);
mBrightnessChangeExecutor = brightnessChangeExecutor;
+ mPersistBrightnessNitsForDefaultDisplay = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay);
}
/**
@@ -263,6 +277,12 @@
// Update the setting, which will eventually call back into DPC to have us actually
// update the display with the new value.
mBrightnessSetting.setBrightness(brightnessValue);
+ if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
+ float nits = convertToNits(brightnessValue);
+ if (nits >= 0) {
+ mBrightnessSetting.setBrightnessNitsForDefaultDisplay(nits);
+ }
+ }
}
/**
@@ -281,6 +301,42 @@
}
/**
+ * Set the {@link AutomaticBrightnessController} which is needed to perform nit-to-float-scale
+ * conversion.
+ * @param automaticBrightnessController The ABC
+ */
+ public void setAutomaticBrightnessController(
+ AutomaticBrightnessController automaticBrightnessController) {
+ mAutomaticBrightnessController = automaticBrightnessController;
+ loadNitBasedBrightnessSetting();
+ }
+
+ /**
+ * Convert a brightness float scale value to a nit value.
+ * @param brightness The float scale value
+ * @return The nit value or -1f if no conversion is possible.
+ */
+ public float convertToNits(float brightness) {
+ if (mAutomaticBrightnessController == null) {
+ return -1f;
+ }
+ return mAutomaticBrightnessController.convertToNits(brightness);
+ }
+
+ /**
+ * Convert a brightness nit value to a float scale value.
+ * @param nits The nit value
+ * @return The float scale value or {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} if no
+ * conversion is possible.
+ */
+ public float convertToFloatScale(float nits) {
+ if (mAutomaticBrightnessController == null) {
+ return PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ }
+ return mAutomaticBrightnessController.convertToFloatScale(nits);
+ }
+
+ /**
* Stops the associated listeners when the display is stopped. Invoked when the {@link
* #mDisplayId} is being removed.
*/
@@ -300,6 +356,8 @@
writer.println("DisplayBrightnessController:");
writer.println(" mDisplayId=: " + mDisplayId);
writer.println(" mScreenBrightnessDefault=" + mScreenBrightnessDefault);
+ writer.println(" mPersistBrightnessNitsForDefaultDisplay="
+ + mPersistBrightnessNitsForDefaultDisplay);
synchronized (mLock) {
writer.println(" mPendingScreenBrightness=" + mPendingScreenBrightness);
writer.println(" mCurrentScreenBrightness=" + mCurrentScreenBrightness);
@@ -353,4 +411,29 @@
private void notifyCurrentScreenBrightness() {
mBrightnessChangeExecutor.execute(mOnBrightnessChangeRunnable);
}
+
+ /**
+ * Loads the brightness value. If this is the default display and the config says that we should
+ * persist the nit value, the nit value for the default display will be loaded.
+ */
+ private void loadNitBasedBrightnessSetting() {
+ if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
+ float brightnessNitsForDefaultDisplay =
+ mBrightnessSetting.getBrightnessNitsForDefaultDisplay();
+ if (brightnessNitsForDefaultDisplay >= 0) {
+ float brightnessForDefaultDisplay = convertToFloatScale(
+ brightnessNitsForDefaultDisplay);
+ if (BrightnessUtils.isValidBrightnessValue(brightnessForDefaultDisplay)) {
+ mBrightnessSetting.setBrightness(brightnessForDefaultDisplay);
+ synchronized (mLock) {
+ mCurrentScreenBrightness = brightnessForDefaultDisplay;
+ }
+ return;
+ }
+ }
+ }
+ synchronized (mLock) {
+ mCurrentScreenBrightness = getScreenBrightnessSetting();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/dreams/DreamShellCommand.java b/services/core/java/com/android/server/dreams/DreamShellCommand.java
index ab84ae4..df70a32 100644
--- a/services/core/java/com/android/server/dreams/DreamShellCommand.java
+++ b/services/core/java/com/android/server/dreams/DreamShellCommand.java
@@ -39,26 +39,24 @@
@Override
public int onCommand(String cmd) {
- final int callingUid = Binder.getCallingUid();
- if (callingUid != Process.ROOT_UID) {
- Slog.e(TAG, "Must be root before calling Dream shell commands");
- return -1;
- }
-
- if (TextUtils.isEmpty(cmd)) {
- return super.handleDefaultCommands(cmd);
- }
if (DEBUG) {
Slog.d(TAG, "onCommand:" + cmd);
}
- switch (cmd) {
- case "start-dreaming":
- return startDreaming();
- case "stop-dreaming":
- return stopDreaming();
- default:
- return super.handleDefaultCommands(cmd);
+ try {
+ switch (cmd) {
+ case "start-dreaming":
+ enforceCallerIsRoot();
+ return startDreaming();
+ case "stop-dreaming":
+ enforceCallerIsRoot();
+ return stopDreaming();
+ default:
+ return super.handleDefaultCommands(cmd);
+ }
+ } catch (SecurityException e) {
+ getOutPrintWriter().println(e);
+ return -1;
}
}
@@ -72,6 +70,12 @@
return 0;
}
+ private void enforceCallerIsRoot() {
+ if (Binder.getCallingUid() != Process.ROOT_UID) {
+ throw new SecurityException("Must be root to call Dream shell commands");
+ }
+ }
+
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 088740e..f873a1b 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -45,14 +45,17 @@
import android.os.Looper;
import android.os.Message;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.InputDevice;
import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import android.widget.Toast;
@@ -75,6 +78,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.stream.Stream;
/**
@@ -102,8 +106,10 @@
private final PersistentDataStore mDataStore;
private final Handler mHandler;
- private final List<InputDevice> mKeyboardsWithMissingLayouts = new ArrayList<>();
- private boolean mKeyboardLayoutNotificationShown = false;
+ // Connected keyboards with associated keyboard layouts (either auto-detected or manually
+ // selected layout). If the mapped value is null/empty, it means that no layout has been
+ // configured for the keyboard and user might need to manually configure it from the Settings.
+ private final SparseArray<Set<String>> mConfiguredKeyboards = new SparseArray<>();
private Toast mSwitchedKeyboardLayoutToast;
// This cache stores "best-matched" layouts so that we don't need to run the matching
@@ -158,10 +164,8 @@
@Override
public void onInputDeviceRemoved(int deviceId) {
- if (!useNewSettingsUi()) {
- mKeyboardsWithMissingLayouts.removeIf(device -> device.getId() == deviceId);
- maybeUpdateNotification();
- }
+ mConfiguredKeyboards.remove(deviceId);
+ maybeUpdateNotification();
}
@Override
@@ -178,13 +182,53 @@
if (layout != null) {
setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
} else {
- mKeyboardsWithMissingLayouts.add(inputDevice);
+ mConfiguredKeyboards.put(inputDevice.getId(), new HashSet<>());
}
}
- maybeUpdateNotification();
+ }
+ } else {
+ final InputDeviceIdentifier identifier = inputDevice.getIdentifier();
+ final String key = getLayoutDescriptor(identifier);
+ Set<String> selectedLayouts = new HashSet<>();
+ boolean needToShowMissingLayoutNotification = false;
+ for (ImeInfo imeInfo : getImeInfoListForLayoutMapping()) {
+ // Check if the layout has been previously configured
+ String layout = getKeyboardLayoutForInputDeviceInternal(identifier,
+ new ImeInfo(imeInfo.mUserId, imeInfo.mImeSubtypeHandle,
+ imeInfo.mImeSubtype));
+ if (layout == null) {
+ needToShowMissingLayoutNotification = true;
+ continue;
+ }
+ selectedLayouts.add(layout);
+ }
+
+ if (needToShowMissingLayoutNotification) {
+ // If even one layout not configured properly we will show configuration
+ // notification allowing user to set the keyboard layout.
+ selectedLayouts.clear();
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Layouts selected for input device: " + identifier + " -> selectedLayouts: "
+ + selectedLayouts);
+ }
+ mConfiguredKeyboards.set(inputDevice.getId(), selectedLayouts);
+
+ synchronized (mDataStore) {
+ try {
+ if (!mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
+ // No need to show the notification only if layout selection didn't change
+ // from the previous configuration
+ return;
+ }
+ } finally {
+ mDataStore.saveIfNeeded();
+ }
}
}
- // TODO(b/259530132): Show notification for new Settings UI
+ maybeUpdateNotification();
}
private String getDefaultKeyboardLayout(final InputDevice inputDevice) {
@@ -483,15 +527,17 @@
key.append("vendor:").append(identifier.getVendorId()).append(",product:").append(
identifier.getProductId());
- InputDevice inputDevice = getInputDevice(identifier);
- Objects.requireNonNull(inputDevice, "Input device must not be null");
- // Some keyboards can have same product ID and vendor ID but different Keyboard info like
- // language tag and layout type.
- if (!TextUtils.isEmpty(inputDevice.getKeyboardLanguageTag())) {
- key.append(",languageTag:").append(inputDevice.getKeyboardLanguageTag());
- }
- if (!TextUtils.isEmpty(inputDevice.getKeyboardLayoutType())) {
- key.append(",layoutType:").append(inputDevice.getKeyboardLanguageTag());
+ if (useNewSettingsUi()) {
+ InputDevice inputDevice = getInputDevice(identifier);
+ Objects.requireNonNull(inputDevice, "Input device must not be null");
+ // Some keyboards can have same product ID and vendor ID but different Keyboard info
+ // like language tag and layout type.
+ if (!TextUtils.isEmpty(inputDevice.getKeyboardLanguageTag())) {
+ key.append(",languageTag:").append(inputDevice.getKeyboardLanguageTag());
+ }
+ if (!TextUtils.isEmpty(inputDevice.getKeyboardLayoutType())) {
+ key.append(",layoutType:").append(inputDevice.getKeyboardLanguageTag());
+ }
}
return key.toString();
}
@@ -669,6 +715,12 @@
public String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
String keyboardLayoutDescriptor;
if (useNewSettingsUi()) {
+ InputDevice inputDevice = getInputDevice(identifier);
+ if (inputDevice == null) {
+ // getKeyboardLayoutOverlay() called before input device added completely. Need
+ // to wait till the device is added which will call reloadKeyboardLayouts()
+ return null;
+ }
if (mCurrentImeInfo == null) {
// Haven't received onInputMethodSubtypeChanged() callback from IMMS. Will reload
// keyboard layouts once we receive the callback.
@@ -991,66 +1043,140 @@
}
private void maybeUpdateNotification() {
+ if (mConfiguredKeyboards.size() == 0) {
+ hideKeyboardLayoutNotification();
+ return;
+ }
+ for (int i = 0; i < mConfiguredKeyboards.size(); i++) {
+ // If we have a keyboard with no selected layouts, we should always show missing
+ // layout notification even if there are other keyboards that are configured properly.
+ if (mConfiguredKeyboards.valueAt(i).isEmpty()) {
+ showMissingKeyboardLayoutNotification();
+ return;
+ }
+ }
+ showConfiguredKeyboardLayoutNotification();
+ }
+
+ // Must be called on handler.
+ private void showMissingKeyboardLayoutNotification() {
+ final Resources r = mContext.getResources();
+ final String missingKeyboardLayoutNotificationContent = r.getString(
+ R.string.select_keyboard_layout_notification_message);
+
+ if (mConfiguredKeyboards.size() == 1) {
+ final InputDevice device = getInputDevice(mConfiguredKeyboards.keyAt(0));
+ if (device == null) {
+ return;
+ }
+ showKeyboardLayoutNotification(
+ r.getString(
+ R.string.select_keyboard_layout_notification_title,
+ device.getName()),
+ missingKeyboardLayoutNotificationContent,
+ device);
+ } else {
+ showKeyboardLayoutNotification(
+ r.getString(R.string.select_multiple_keyboards_layout_notification_title),
+ missingKeyboardLayoutNotificationContent,
+ null);
+ }
+ }
+
+ private void showKeyboardLayoutNotification(@NonNull String intentTitle,
+ @NonNull String intentContent, @Nullable InputDevice targetDevice) {
+ final NotificationManager notificationManager = mContext.getSystemService(
+ NotificationManager.class);
+ if (notificationManager == null) {
+ return;
+ }
+
+ final Intent intent = new Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS);
+
+ if (targetDevice != null) {
+ intent.putExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, targetDevice.getIdentifier());
+ }
+
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ final PendingIntent keyboardLayoutIntent = PendingIntent.getActivityAsUser(mContext, 0,
+ intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
+
+ Notification notification =
+ new Notification.Builder(mContext, SystemNotificationChannels.PHYSICAL_KEYBOARD)
+ .setContentTitle(intentTitle)
+ .setContentText(intentContent)
+ .setContentIntent(keyboardLayoutIntent)
+ .setSmallIcon(R.drawable.ic_settings_language)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setAutoCancel(true)
+ .build();
+ notificationManager.notifyAsUser(null,
+ SystemMessageProto.SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT,
+ notification, UserHandle.ALL);
+ }
+
+ // Must be called on handler.
+ private void hideKeyboardLayoutNotification() {
NotificationManager notificationManager = mContext.getSystemService(
NotificationManager.class);
if (notificationManager == null) {
return;
}
- if (!mKeyboardsWithMissingLayouts.isEmpty()) {
- if (mKeyboardsWithMissingLayouts.size() > 1) {
- // We have more than one keyboard missing a layout, so drop the
- // user at the generic input methods page, so they can pick which
- // one to set.
- showMissingKeyboardLayoutNotification(notificationManager, null);
- } else {
- showMissingKeyboardLayoutNotification(notificationManager,
- mKeyboardsWithMissingLayouts.get(0));
- }
- } else if (mKeyboardLayoutNotificationShown) {
- hideMissingKeyboardLayoutNotification(notificationManager);
- }
+
+ notificationManager.cancelAsUser(null,
+ SystemMessageProto.SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT,
+ UserHandle.ALL);
}
- // Must be called on handler.
- private void showMissingKeyboardLayoutNotification(NotificationManager notificationManager,
- InputDevice device) {
- if (!mKeyboardLayoutNotificationShown) {
- final Intent intent = new Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS);
- if (device != null) {
- intent.putExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, device.getIdentifier());
- }
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
- | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- final PendingIntent keyboardLayoutIntent = PendingIntent.getActivityAsUser(mContext, 0,
- intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
+ private void showConfiguredKeyboardLayoutNotification() {
+ final Resources r = mContext.getResources();
- Resources r = mContext.getResources();
- Notification notification =
- new Notification.Builder(mContext, SystemNotificationChannels.PHYSICAL_KEYBOARD)
- .setContentTitle(r.getString(
- R.string.select_keyboard_layout_notification_title))
- .setContentText(r.getString(
- R.string.select_keyboard_layout_notification_message))
- .setContentIntent(keyboardLayoutIntent)
- .setSmallIcon(R.drawable.ic_settings_language)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color))
- .build();
- notificationManager.notifyAsUser(null,
- SystemMessageProto.SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT,
- notification, UserHandle.ALL);
- mKeyboardLayoutNotificationShown = true;
+ if (mConfiguredKeyboards.size() != 1) {
+ showKeyboardLayoutNotification(
+ r.getString(R.string.keyboard_layout_notification_multiple_selected_title),
+ r.getString(R.string.keyboard_layout_notification_multiple_selected_message),
+ null);
+ return;
}
+
+ final InputDevice inputDevice = getInputDevice(mConfiguredKeyboards.keyAt(0));
+ final Set<String> selectedLayouts = mConfiguredKeyboards.valueAt(0);
+ if (inputDevice == null || selectedLayouts == null || selectedLayouts.isEmpty()) {
+ return;
+ }
+
+ showKeyboardLayoutNotification(
+ r.getString(
+ R.string.keyboard_layout_notification_selected_title,
+ inputDevice.getName()),
+ createConfiguredNotificationText(mContext, selectedLayouts),
+ inputDevice);
}
- // Must be called on handler.
- private void hideMissingKeyboardLayoutNotification(NotificationManager notificationManager) {
- if (mKeyboardLayoutNotificationShown) {
- mKeyboardLayoutNotificationShown = false;
- notificationManager.cancelAsUser(null,
- SystemMessageProto.SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT,
- UserHandle.ALL);
+ private String createConfiguredNotificationText(@NonNull Context context,
+ @NonNull Set<String> selectedLayouts) {
+ final Resources r = context.getResources();
+ List<String> layoutNames = new ArrayList<>();
+ selectedLayouts.forEach(
+ (layoutDesc) -> layoutNames.add(getKeyboardLayout(layoutDesc).getLabel()));
+ Collections.sort(layoutNames);
+ switch (layoutNames.size()) {
+ case 1:
+ return r.getString(R.string.keyboard_layout_notification_one_selected_message,
+ layoutNames.get(0));
+ case 2:
+ return r.getString(R.string.keyboard_layout_notification_two_selected_message,
+ layoutNames.get(0), layoutNames.get(1));
+ case 3:
+ return r.getString(R.string.keyboard_layout_notification_three_selected_message,
+ layoutNames.get(0), layoutNames.get(1), layoutNames.get(2));
+ default:
+ return r.getString(
+ R.string.keyboard_layout_notification_more_than_three_selected_message,
+ layoutNames.get(0), layoutNames.get(1), layoutNames.get(2));
}
}
@@ -1094,6 +1220,31 @@
identifier.getDescriptor()) : null;
}
+ private List<ImeInfo> getImeInfoListForLayoutMapping() {
+ List<ImeInfo> imeInfoList = new ArrayList<>();
+ UserManager userManager = Objects.requireNonNull(
+ mContext.getSystemService(UserManager.class));
+ InputMethodManager inputMethodManager = Objects.requireNonNull(
+ mContext.getSystemService(InputMethodManager.class));
+ for (UserHandle userHandle : userManager.getUserHandles(true /* excludeDying */)) {
+ int userId = userHandle.getIdentifier();
+ for (InputMethodInfo imeInfo : inputMethodManager.getEnabledInputMethodListAsUser(
+ userId)) {
+ for (InputMethodSubtype imeSubtype :
+ inputMethodManager.getEnabledInputMethodSubtypeList(
+ imeInfo, true /* allowsImplicitlyEnabledSubtypes */)) {
+ if (!imeSubtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
+ continue;
+ }
+ imeInfoList.add(
+ new ImeInfo(userId, InputMethodSubtypeHandle.of(imeInfo, imeSubtype),
+ imeSubtype));
+ }
+ }
+ }
+ return imeInfoList;
+ }
+
private String createLayoutKey(InputDeviceIdentifier identifier, int userId,
@NonNull InputMethodSubtypeHandle subtypeHandle) {
Objects.requireNonNull(subtypeHandle, "subtypeHandle must not be null");
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index a2b18362..bce210d 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -16,6 +16,7 @@
package com.android.server.input;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.input.TouchCalibration;
import android.util.ArrayMap;
@@ -43,6 +44,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -155,6 +157,16 @@
return false;
}
+ public boolean setSelectedKeyboardLayouts(String inputDeviceDescriptor,
+ @NonNull Set<String> selectedLayouts) {
+ InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
+ if (state.setSelectedKeyboardLayouts(selectedLayouts)) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
public String[] getKeyboardLayouts(String inputDeviceDescriptor) {
InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
if (state == null) {
@@ -408,6 +420,8 @@
private final Map<String, String> mKeyboardLayoutMap = new ArrayMap<>();
+ private Set<String> mSelectedKeyboardLayouts;
+
public TouchCalibration getTouchCalibration(int surfaceRotation) {
try {
return mTouchCalibration[surfaceRotation];
@@ -439,6 +453,14 @@
return !Objects.equals(mKeyboardLayoutMap.put(key, keyboardLayout), keyboardLayout);
}
+ public boolean setSelectedKeyboardLayouts(@NonNull Set<String> selectedLayouts) {
+ if (Objects.equals(mSelectedKeyboardLayouts, selectedLayouts)) {
+ return false;
+ }
+ mSelectedKeyboardLayouts = new HashSet<>(selectedLayouts);
+ return true;
+ }
+
@Nullable
public String getCurrentKeyboardLayout() {
return mCurrentKeyboardLayout;
@@ -588,6 +610,16 @@
"Missing layout attribute on keyed-keyboard-layout.");
}
mKeyboardLayoutMap.put(key, layout);
+ } else if (parser.getName().equals("selected-keyboard-layout")) {
+ String layout = parser.getAttributeValue(null, "layout");
+ if (layout == null) {
+ throw new XmlPullParserException(
+ "Missing layout attribute on selected-keyboard-layout.");
+ }
+ if (mSelectedKeyboardLayouts == null) {
+ mSelectedKeyboardLayouts = new HashSet<>();
+ }
+ mSelectedKeyboardLayouts.add(layout);
} else if (parser.getName().equals("light-info")) {
int lightId = parser.getAttributeInt(null, "light-id");
int lightBrightness = parser.getAttributeInt(null, "light-brightness");
@@ -668,6 +700,14 @@
serializer.endTag(null, "keyed-keyboard-layout");
}
+ if (mSelectedKeyboardLayouts != null) {
+ for (String layout : mSelectedKeyboardLayouts) {
+ serializer.startTag(null, "selected-keyboard-layout");
+ serializer.attribute(null, "layout", layout);
+ serializer.endTag(null, "selected-keyboard-layout");
+ }
+ }
+
for (int i = 0; i < mKeyboardBacklightBrightnessMap.size(); i++) {
serializer.startTag(null, "light-info");
serializer.attributeInt(null, "light-id", mKeyboardBacklightBrightnessMap.keyAt(i));
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 94f12dd..653b718 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -1148,7 +1148,7 @@
super.getPreloadedNanoAppIds_enforcePermission();
Objects.requireNonNull(hubInfo, "hubInfo cannot be null");
- long[] nanoappIds = mContextHubWrapper.getPreloadedNanoappIds();
+ long[] nanoappIds = mContextHubWrapper.getPreloadedNanoappIds(hubInfo.getId());
if (nanoappIds == null) {
return new long[0];
}
@@ -1261,13 +1261,19 @@
return;
}
- long[] preloadedNanoappIds = mContextHubWrapper.getPreloadedNanoappIds();
- if (preloadedNanoappIds == null) {
- return;
- }
- for (long preloadedNanoappId : preloadedNanoappIds) {
- pw.print("ID: 0x");
- pw.println(Long.toHexString(preloadedNanoappId));
+ for (int contextHubId: mContextHubIdToInfoMap.keySet()) {
+ long[] preloadedNanoappIds = mContextHubWrapper.getPreloadedNanoappIds(contextHubId);
+ if (preloadedNanoappIds == null) {
+ return;
+ }
+
+ pw.print("Context Hub (id=");
+ pw.print(contextHubId);
+ pw.println("):");
+ for (long preloadedNanoappId : preloadedNanoappIds) {
+ pw.print(" ID: 0x");
+ pw.println(Long.toHexString(preloadedNanoappId));
+ }
}
}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 1e32ad6..eb1a0e2 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -363,9 +363,11 @@
* Provides the list of preloaded nanoapp IDs on the system. The output of this API must
* not change.
*
- * @return The list of preloaded nanoapp IDs
+ * @param contextHubId The context Hub ID.
+ *
+ * @return The list of preloaded nanoapp IDs.
*/
- public abstract long[] getPreloadedNanoappIds();
+ public abstract long[] getPreloadedNanoappIds(int contextHubId);
/**
* Registers a callback with the Context Hub.
@@ -714,14 +716,14 @@
}
}
- public long[] getPreloadedNanoappIds() {
+ public long[] getPreloadedNanoappIds(int contextHubId) {
android.hardware.contexthub.IContextHub hub = getHub();
if (hub == null) {
return null;
}
try {
- return hub.getPreloadedNanoappIds();
+ return hub.getPreloadedNanoappIds(contextHubId);
} catch (RemoteException e) {
Log.e(TAG, "Exception while getting preloaded nanoapp IDs: " + e.getMessage());
return null;
@@ -924,7 +926,7 @@
mHub.queryApps(contextHubId));
}
- public long[] getPreloadedNanoappIds() {
+ public long[] getPreloadedNanoappIds(int contextHubId) {
return new long[0];
}
diff --git a/services/core/java/com/android/server/media/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java
new file mode 100644
index 0000000..b9c9bae
--- /dev/null
+++ b/services/core/java/com/android/server/media/AudioAttributesUtils.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 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.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.MediaRoute2Info;
+
+/* package */ final class AudioAttributesUtils {
+
+ /* package */ static final AudioAttributes ATTRIBUTES_MEDIA = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .build();
+
+ private AudioAttributesUtils() {
+ // no-op to prevent instantiation.
+ }
+
+ @MediaRoute2Info.Type
+ /* package */ static int mapToMediaRouteType(
+ @NonNull AudioDeviceAttributes audioDeviceAttributes) {
+ switch (audioDeviceAttributes.getType()) {
+ case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
+ case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
+ return MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+ case AudioDeviceInfo.TYPE_WIRED_HEADSET:
+ return MediaRoute2Info.TYPE_WIRED_HEADSET;
+ case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
+ return MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+ case AudioDeviceInfo.TYPE_DOCK:
+ case AudioDeviceInfo.TYPE_DOCK_ANALOG:
+ return MediaRoute2Info.TYPE_DOCK;
+ case AudioDeviceInfo.TYPE_HDMI:
+ return MediaRoute2Info.TYPE_HDMI;
+ case AudioDeviceInfo.TYPE_USB_DEVICE:
+ return MediaRoute2Info.TYPE_USB_DEVICE;
+ case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
+ return MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+ case AudioDeviceInfo.TYPE_BLE_HEADSET:
+ return MediaRoute2Info.TYPE_BLE_HEADSET;
+ case AudioDeviceInfo.TYPE_HEARING_AID:
+ return MediaRoute2Info.TYPE_HEARING_AID;
+ default:
+ return MediaRoute2Info.TYPE_UNKNOWN;
+ }
+ }
+
+
+ /* package */ static boolean isDeviceOutputAttributes(
+ @Nullable AudioDeviceAttributes audioDeviceAttributes) {
+ if (audioDeviceAttributes == null) {
+ return false;
+ }
+
+ if (audioDeviceAttributes.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) {
+ return false;
+ }
+
+ switch (audioDeviceAttributes.getType()) {
+ case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
+ case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
+ case AudioDeviceInfo.TYPE_WIRED_HEADSET:
+ case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
+ case AudioDeviceInfo.TYPE_DOCK:
+ case AudioDeviceInfo.TYPE_DOCK_ANALOG:
+ case AudioDeviceInfo.TYPE_HDMI:
+ case AudioDeviceInfo.TYPE_USB_DEVICE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /* package */ static boolean isBluetoothOutputAttributes(
+ @Nullable AudioDeviceAttributes audioDeviceAttributes) {
+ if (audioDeviceAttributes == null) {
+ return false;
+ }
+
+ if (audioDeviceAttributes.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) {
+ return false;
+ }
+
+ switch (audioDeviceAttributes.getType()) {
+ case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
+ case AudioDeviceInfo.TYPE_BLE_HEADSET:
+ case AudioDeviceInfo.TYPE_BLE_SPEAKER:
+ case AudioDeviceInfo.TYPE_HEARING_AID:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java
index d4a1184..66985e0 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteController.java
@@ -53,7 +53,16 @@
return new NoOpBluetoothRouteController();
}
- return new LegacyBluetoothRouteController(context, btAdapter, listener);
+ MediaFeatureFlagManager flagManager = MediaFeatureFlagManager.getInstance();
+ boolean isUsingLegacyController = flagManager.getBoolean(
+ MediaFeatureFlagManager.FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER,
+ true);
+
+ if (isUsingLegacyController) {
+ return new LegacyBluetoothRouteController(context, btAdapter, listener);
+ } else {
+ return new AudioPoliciesBluetoothRouteController(context, btAdapter, listener);
+ }
}
/**
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index d7893ee..3875c84 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -44,10 +44,22 @@
IAudioService audioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
- return new LegacyDeviceRouteController(context,
- audioManager,
- audioService,
- onDeviceRouteChangedListener);
+ MediaFeatureFlagManager flagManager = MediaFeatureFlagManager.getInstance();
+ boolean isUsingLegacyController = flagManager.getBoolean(
+ MediaFeatureFlagManager.FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER,
+ true);
+
+ if (isUsingLegacyController) {
+ return new LegacyDeviceRouteController(context,
+ audioManager,
+ audioService,
+ onDeviceRouteChangedListener);
+ } else {
+ return new AudioPoliciesDeviceRouteController(context,
+ audioManager,
+ audioService,
+ onDeviceRouteChangedListener);
+ }
}
/**
diff --git a/services/core/java/com/android/server/media/MediaFeatureFlagManager.java b/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
index 723cda0..70ee38f 100644
--- a/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
+++ b/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
@@ -32,7 +32,7 @@
private static final String NAMESPACE_MEDIA_BETTER_TOGETHER = "media_better_together";
@StringDef(prefix = "FEATURE_", value = {
- FEATURE_IS_USING_LEGACY_BLUETOOTH_CONTROLLER
+ FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER
})
@Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
@Retention(RetentionPolicy.SOURCE)
@@ -43,7 +43,7 @@
* 'Audio Strategies'-aware controller.
*/
/* package */ static final @MediaFeatureFlag String
- FEATURE_IS_USING_LEGACY_BLUETOOTH_CONTROLLER =
+ FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER =
"BluetoothRouteController__enable_legacy_bluetooth_routes_controller";
private static final MediaFeatureFlagManager sInstance = new MediaFeatureFlagManager();
@@ -52,7 +52,7 @@
// Empty to prevent instantiation.
}
- /* package */ MediaFeatureFlagManager getInstance() {
+ /* package */ static MediaFeatureFlagManager getInstance() {
return sInstance;
}
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 6619e6c..5d5c621 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -16,11 +16,14 @@
package com.android.server.media;
+import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
@@ -37,6 +40,7 @@
import com.android.internal.annotations.GuardedBy;
+import java.util.List;
import java.util.Objects;
/**
@@ -71,6 +75,26 @@
private final AudioManagerBroadcastReceiver mAudioReceiver =
new AudioManagerBroadcastReceiver();
+ private final AudioManager.OnDevicesForAttributesChangedListener
+ mOnDevicesForAttributesChangedListener =
+ new AudioManager.OnDevicesForAttributesChangedListener() {
+ @Override
+ public void onDevicesForAttributesChanged(@NonNull AudioAttributes attributes,
+ @NonNull List<AudioDeviceAttributes> devices) {
+ if (attributes.getUsage() != AudioAttributes.USAGE_MEDIA) {
+ return;
+ }
+
+ mHandler.post(() -> {
+ updateSelectedAudioDevice(devices);
+ notifyProviderState();
+ if (updateSessionInfosIfNeeded()) {
+ notifySessionInfoUpdated();
+ }
+ });
+ }
+ };
+
private final Object mRequestLock = new Object();
@GuardedBy("mRequestLock")
private volatile SessionCreationRequest mPendingSessionCreationRequest;
@@ -100,8 +124,15 @@
});
});
+ mAudioManager.addOnDevicesForAttributesChangedListener(
+ AudioAttributesUtils.ATTRIBUTES_MEDIA, mContext.getMainExecutor(),
+ mOnDevicesForAttributesChangedListener);
+
// These methods below should be called after all fields are initialized, as they
// access the fields inside.
+ List<AudioDeviceAttributes> devices =
+ mAudioManager.getDevicesForAttributes(AudioAttributesUtils.ATTRIBUTES_MEDIA);
+ updateSelectedAudioDevice(devices);
updateProviderState();
updateSessionInfosIfNeeded();
}
@@ -239,6 +270,26 @@
}
}
+ private void updateSelectedAudioDevice(@NonNull List<AudioDeviceAttributes> devices) {
+ if (devices.isEmpty()) {
+ Slog.w(TAG, "The list of preferred devices was empty.");
+ return;
+ }
+
+ AudioDeviceAttributes audioDeviceAttributes = devices.get(0);
+
+ if (AudioAttributesUtils.isDeviceOutputAttributes(audioDeviceAttributes)) {
+ mDeviceRouteController.selectRoute(
+ AudioAttributesUtils.mapToMediaRouteType(audioDeviceAttributes));
+ mBluetoothRouteController.selectRoute(null);
+ } else if (AudioAttributesUtils.isBluetoothOutputAttributes(audioDeviceAttributes)) {
+ mDeviceRouteController.selectRoute(null);
+ mBluetoothRouteController.selectRoute(audioDeviceAttributes.getAddress());
+ } else {
+ Slog.w(TAG, "Unknown audio attributes: " + audioDeviceAttributes);
+ }
+ }
+
private void updateProviderState() {
MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 56f3296..92be094 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -1650,15 +1650,7 @@
mContext, 0, snoozeIntent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE));
final Intent viewIntent = buildViewDataUsageIntent(res, policy.template);
- // TODO: Resolve to single code path.
- if (UserManager.isHeadlessSystemUserMode()) {
- builder.setContentIntent(PendingIntent.getActivityAsUser(
- mContext, 0, viewIntent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE,
- /* options= */ null, UserHandle.CURRENT));
- } else {
- builder.setContentIntent(PendingIntent.getActivity(
- mContext, 0, viewIntent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE));
- }
+ setContentIntent(builder, viewIntent);
break;
}
case TYPE_LIMIT: {
@@ -1679,15 +1671,7 @@
builder.setSmallIcon(R.drawable.stat_notify_disabled_data);
final Intent intent = buildNetworkOverLimitIntent(res, policy.template);
- // TODO: Resolve to single code path.
- if (UserManager.isHeadlessSystemUserMode()) {
- builder.setContentIntent(PendingIntent.getActivityAsUser(
- mContext, 0, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE,
- /* options= */ null, UserHandle.CURRENT));
- } else {
- builder.setContentIntent(PendingIntent.getActivity(
- mContext, 0, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE));
- }
+ setContentIntent(builder, intent);
break;
}
case TYPE_LIMIT_SNOOZED: {
@@ -1711,15 +1695,7 @@
builder.setChannelId(SystemNotificationChannels.NETWORK_STATUS);
final Intent intent = buildViewDataUsageIntent(res, policy.template);
- // TODO: Resolve to single code path.
- if (UserManager.isHeadlessSystemUserMode()) {
- builder.setContentIntent(PendingIntent.getActivityAsUser(
- mContext, 0, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE,
- /* options= */ null, UserHandle.CURRENT));
- } else {
- builder.setContentIntent(PendingIntent.getActivity(
- mContext, 0, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE));
- }
+ setContentIntent(builder, intent);
break;
}
case TYPE_RAPID: {
@@ -1739,15 +1715,7 @@
mContext, 0, snoozeIntent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE));
final Intent viewIntent = buildViewDataUsageIntent(res, policy.template);
- // TODO: Resolve to single code path.
- if (UserManager.isHeadlessSystemUserMode()) {
- builder.setContentIntent(PendingIntent.getActivityAsUser(
- mContext, 0, viewIntent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE,
- /* options= */ null, UserHandle.CURRENT));
- } else {
- builder.setContentIntent(PendingIntent.getActivity(
- mContext, 0, viewIntent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE));
- }
+ setContentIntent(builder, viewIntent);
break;
}
default: {
@@ -1765,6 +1733,17 @@
mActiveNotifs.add(notificationId);
}
+ private void setContentIntent(Notification.Builder builder, Intent intent) {
+ if (UserManager.isHeadlessSystemUserMode()) {
+ builder.setContentIntent(PendingIntent.getActivityAsUser(
+ mContext, 0, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE,
+ /* options= */ null, UserHandle.CURRENT));
+ } else {
+ builder.setContentIntent(PendingIntent.getActivity(
+ mContext, 0, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE));
+ }
+ }
+
private void cancelNotification(NotificationId notificationId) {
mContext.getSystemService(NotificationManager.class).cancel(notificationId.getTag(),
notificationId.getId());
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 9e01c7a..84bee50 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -39,6 +39,7 @@
import android.app.AppGlobals;
import android.app.IApplicationThread;
import android.app.PendingIntent;
+import android.app.admin.DevicePolicyCache;
import android.app.admin.DevicePolicyManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ActivityNotFoundException;
@@ -85,6 +86,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -107,6 +109,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
@@ -623,7 +626,7 @@
// package does not exist; should not happen
return null;
}
- return new LauncherActivityInfoInternal(activityInfo, incrementalStatesInfo);
+ return new LauncherActivityInfoInternal(activityInfo, incrementalStatesInfo, user);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -676,7 +679,7 @@
continue;
}
results.add(new LauncherActivityInfoInternal(ri.activityInfo,
- incrementalStatesInfo));
+ incrementalStatesInfo, user));
}
return results;
}
@@ -1078,6 +1081,55 @@
}
@Override
+ @NonNull
+ public Map<String, LauncherActivityInfoInternal> getActivityOverrides(String callingPackage,
+ int userId) {
+ ensureShortcutPermission(callingPackage);
+ int callingUid = Binder.getCallingUid();
+ final long callerIdentity = Binder.clearCallingIdentity();
+ try {
+ Map<String, LauncherActivityInfoInternal> shortcutOverridesInfo = new ArrayMap<>();
+ UserHandle managedUserHandle = getManagedProfile(userId);
+ if (managedUserHandle == null) {
+ return shortcutOverridesInfo;
+ }
+
+ List<String> packagesToOverride =
+ DevicePolicyCache.getInstance().getLauncherShortcutOverrides();
+ for (String packageName : packagesToOverride) {
+ Intent intent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_LAUNCHER)
+ .setPackage(packageName);
+
+ List<LauncherActivityInfoInternal> possibleShortcutOverrides =
+ queryIntentLauncherActivities(
+ intent,
+ callingUid,
+ managedUserHandle
+ );
+
+ if (!possibleShortcutOverrides.isEmpty()) {
+ shortcutOverridesInfo.put(packageName, possibleShortcutOverrides.get(0));
+ }
+ }
+ return shortcutOverridesInfo;
+ } finally {
+ Binder.restoreCallingIdentity(callerIdentity);
+ }
+ }
+
+
+ @Nullable
+ private UserHandle getManagedProfile(int userId) {
+ for (UserInfo profile : mUm.getProfiles(userId)) {
+ if (profile.isManagedProfile()) {
+ return profile.getUserHandle();
+ }
+ }
+ return null;
+ }
+
+ @Override
public boolean startShortcut(String callingPackage, String packageName, String featureId,
String shortcutId, Rect sourceBounds, Bundle startActivityOptions,
int targetUserId) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index de5f0c4..d3ee52c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7218,6 +7218,7 @@
* TODO(b/182523293): This should be removed once we finish migration of permission storage.
*/
void writeSettingsLPrTEMP(boolean sync) {
+ snapshotComputer(false);
mPermissionManager.writeLegacyPermissionsTEMP(mSettings.mPermissions);
mSettings.writeLPr(mLiveComputer, sync);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 37a59da..4ec8afd 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1247,6 +1247,11 @@
case LONG_PRESS_POWER_SHUT_OFF:
case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
mPowerKeyHandled = true;
+ // don't actually trigger the shutdown if we are running stability
+ // tests via monkey
+ if (ActivityManager.isUserAMonkey()) {
+ break;
+ }
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
"Power - Long Press - Shut Off");
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
@@ -4518,25 +4523,6 @@
*/
private boolean isWakeKeyWhenScreenOff(int keyCode) {
switch (keyCode) {
- case KeyEvent.KEYCODE_VOLUME_UP:
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- case KeyEvent.KEYCODE_VOLUME_MUTE:
- return mDefaultDisplayPolicy.getDockMode() != Intent.EXTRA_DOCK_STATE_UNDOCKED;
-
- case KeyEvent.KEYCODE_MUTE:
- case KeyEvent.KEYCODE_HEADSETHOOK:
- case KeyEvent.KEYCODE_MEDIA_PLAY:
- case KeyEvent.KEYCODE_MEDIA_PAUSE:
- case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
- case KeyEvent.KEYCODE_MEDIA_STOP:
- case KeyEvent.KEYCODE_MEDIA_NEXT:
- case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
- case KeyEvent.KEYCODE_MEDIA_REWIND:
- case KeyEvent.KEYCODE_MEDIA_RECORD:
- case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
- case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
- return false;
-
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
case KeyEvent.KEYCODE_DPAD_LEFT:
diff --git a/services/core/java/com/android/server/policy/PowerAction.java b/services/core/java/com/android/server/policy/PowerAction.java
index d2de58e..deb86b5 100644
--- a/services/core/java/com/android/server/policy/PowerAction.java
+++ b/services/core/java/com/android/server/policy/PowerAction.java
@@ -15,6 +15,7 @@
*/
package com.android.server.policy;
+import android.app.ActivityManager;
import android.content.Context;
import android.os.UserManager;
import com.android.internal.globalactions.LongPressAction;
@@ -35,6 +36,11 @@
@Override
public boolean onLongPress() {
+ // don't actually trigger the reboot if we are running stability
+ // tests via monkey
+ if (ActivityManager.isUserAMonkey()) {
+ return false;
+ }
UserManager um = mContext.getSystemService(UserManager.class);
if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
mWindowManagerFuncs.rebootSafeMode(true);
@@ -55,6 +61,11 @@
@Override
public void onPress() {
+ // don't actually trigger the shutdown if we are running stability
+ // tests via monkey
+ if (ActivityManager.isUserAMonkey()) {
+ return;
+ }
// shutdown by making sure radio and power are handled accordingly.
mWindowManagerFuncs.shutdown(false /* confirm */);
}
diff --git a/services/core/java/com/android/server/policy/RestartAction.java b/services/core/java/com/android/server/policy/RestartAction.java
index 0f13da8..24c921e 100644
--- a/services/core/java/com/android/server/policy/RestartAction.java
+++ b/services/core/java/com/android/server/policy/RestartAction.java
@@ -15,6 +15,7 @@
*/
package com.android.server.policy;
+import android.app.ActivityManager;
import android.content.Context;
import android.os.UserManager;
import com.android.internal.globalactions.LongPressAction;
@@ -35,6 +36,11 @@
@Override
public boolean onLongPress() {
+ // don't actually trigger the reboot if we are running stability
+ // tests via monkey
+ if (ActivityManager.isUserAMonkey()) {
+ return false;
+ }
UserManager um = mContext.getSystemService(UserManager.class);
if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
mWindowManagerFuncs.rebootSafeMode(true);
@@ -55,6 +61,11 @@
@Override
public void onPress() {
+ // don't actually trigger the reboot if we are running stability
+ // tests via monkey
+ if (ActivityManager.isUserAMonkey()) {
+ return;
+ }
mWindowManagerFuncs.reboot(false /* confirm */);
}
}
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 5096ad1..c2d4ac6 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -17,6 +17,7 @@
package com.android.server.power;
+import android.app.ActivityManagerInternal;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.IActivityManager;
@@ -25,10 +26,12 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManagerInternal;
import android.media.AudioAttributes;
+import android.os.Bundle;
import android.os.FileUtils;
import android.os.Handler;
import android.os.PowerManager;
@@ -51,7 +54,6 @@
import com.android.server.LocalServices;
import com.android.server.RescueParty;
-import com.android.server.pm.PackageManagerService;
import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.File;
@@ -448,13 +450,6 @@
new File(CHECK_POINTS_FILE_BASENAME));
dumpCheckPointsThread.start();
- BroadcastReceiver br = new BroadcastReceiver() {
- @Override public void onReceive(Context context, Intent intent) {
- // We don't allow apps to cancel this, so ignore the result.
- actionDone();
- }
- };
-
/*
* Write a system property in case the system_server reboots before we
* get to the actual hardware restart. If that happens, we'll retry at
@@ -490,8 +485,16 @@
mActionDone = false;
Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendOrderedBroadcastAsUser(intent,
- UserHandle.ALL, null, br, mHandler, 0, null, null);
+ final ActivityManagerInternal activityManagerInternal = LocalServices.getService(
+ ActivityManagerInternal.class);
+ activityManagerInternal.broadcastIntentWithCallback(intent,
+ new IIntentReceiver.Stub() {
+ @Override
+ public void performReceive(Intent intent, int resultCode, String data,
+ Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
+ mHandler.post(ShutdownThread.this::actionDone);
+ }
+ }, null, UserHandle.USER_ALL, null, null, null);
final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
synchronized (mActionDoneSync) {
diff --git a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
index 706aedc..b05b662 100644
--- a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
+++ b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
@@ -22,7 +22,9 @@
import android.content.Context;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.LongSparseArray;
@@ -40,6 +42,8 @@
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -52,13 +56,13 @@
private static final String SUBSYSTEM_ALARM_STRING = "Alarm";
private static final String SUBSYSTEM_ALARM_WIFI = "Wifi";
@VisibleForTesting
- static final long WAKEUP_RETENTION_MS = 3 * 24 * 60 * 60_000; // 3 days.
- @VisibleForTesting
static final long WAKEUP_REASON_HALF_WINDOW_MS = 500;
- private static final long WAKEUP_WRITE_DELAY_MS = 2 * 60 * 1000; // 2 minutes.
+ private static final long WAKEUP_WRITE_DELAY_MS = TimeUnit.MINUTES.toMillis(2);
private final Handler mHandler;
private final IrqDeviceMap mIrqDeviceMap;
+ @VisibleForTesting
+ final Config mConfig = new Config();
private final WakingActivityHistory mRecentWakingActivity = new WakingActivityHistory();
@VisibleForTesting
@@ -72,6 +76,14 @@
mHandler = handler;
}
+ /**
+ * Called on the boot phase SYSTEM_SERVICES_READY.
+ * This ensures that DeviceConfig is ready for calls to read properties.
+ */
+ public synchronized void systemServicesReady() {
+ mConfig.register(new HandlerExecutor(mHandler));
+ }
+
private static int subsystemToStatsReason(int subsystem) {
switch (subsystem) {
case CPU_WAKEUP_SUBSYSTEM_ALARM:
@@ -136,14 +148,15 @@
// we can delete all history that will not be useful in attributing future wakeups.
mRecentWakingActivity.clearAllBefore(elapsedRealtime - WAKEUP_REASON_HALF_WINDOW_MS);
- // Limit history of wakeups and their attribution to the last WAKEUP_RETENTION_MS. Note that
+ // Limit history of wakeups and their attribution to the last retentionDuration. Note that
// the last wakeup and its attribution (if computed) is always stored, even if that wakeup
- // had occurred before WAKEUP_RETENTION_MS.
- int lastIdx = mWakeupEvents.closestIndexOnOrBefore(elapsedRealtime - WAKEUP_RETENTION_MS);
+ // had occurred before retentionDuration.
+ final long retentionDuration = mConfig.WAKEUP_STATS_RETENTION_MS;
+ int lastIdx = mWakeupEvents.closestIndexOnOrBefore(elapsedRealtime - retentionDuration);
for (int i = lastIdx; i >= 0; i--) {
mWakeupEvents.removeAt(i);
}
- lastIdx = mWakeupAttribution.closestIndexOnOrBefore(elapsedRealtime - WAKEUP_RETENTION_MS);
+ lastIdx = mWakeupAttribution.closestIndexOnOrBefore(elapsedRealtime - retentionDuration);
for (int i = lastIdx; i >= 0; i--) {
mWakeupAttribution.removeAt(i);
}
@@ -226,6 +239,9 @@
pw.println("CPU wakeup stats:");
pw.increaseIndent();
+ mConfig.dump(pw);
+ pw.println();
+
mIrqDeviceMap.dump(pw);
pw.println();
@@ -296,7 +312,8 @@
}
private static final class WakingActivityHistory {
- private static final long WAKING_ACTIVITY_RETENTION_MS = 3 * 60 * 60_000; // 3 hours.
+ private static final long WAKING_ACTIVITY_RETENTION_MS = TimeUnit.MINUTES.toMillis(10);
+
private SparseArray<TimeSparseArray<SparseBooleanArray>> mWakingActivity =
new SparseArray<>();
@@ -521,4 +538,52 @@
}
}
}
+
+ static final class Config implements DeviceConfig.OnPropertiesChangedListener {
+ static final String KEY_WAKEUP_STATS_RETENTION_MS = "wakeup_stats_retention_ms";
+
+ private static final String[] PROPERTY_NAMES = {
+ KEY_WAKEUP_STATS_RETENTION_MS,
+ };
+
+ static final long DEFAULT_WAKEUP_STATS_RETENTION_MS = TimeUnit.DAYS.toMillis(3);
+
+ /**
+ * Wakeup stats are retained only for this duration.
+ */
+ public volatile long WAKEUP_STATS_RETENTION_MS = DEFAULT_WAKEUP_STATS_RETENTION_MS;
+
+ void register(Executor executor) {
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BATTERY_STATS,
+ executor, this);
+ onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_BATTERY_STATS,
+ PROPERTY_NAMES));
+ }
+
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ for (String name : properties.getKeyset()) {
+ if (name == null) {
+ continue;
+ }
+ switch (name) {
+ case KEY_WAKEUP_STATS_RETENTION_MS:
+ WAKEUP_STATS_RETENTION_MS = properties.getLong(
+ KEY_WAKEUP_STATS_RETENTION_MS, DEFAULT_WAKEUP_STATS_RETENTION_MS);
+ break;
+ }
+ }
+ }
+
+ void dump(IndentingPrintWriter pw) {
+ pw.println("Config:");
+
+ pw.increaseIndent();
+
+ pw.print(KEY_WAKEUP_STATS_RETENTION_MS, WAKEUP_STATS_RETENTION_MS);
+ pw.println();
+
+ pw.decreaseIndent();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 5bace0e..88d64df 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -151,6 +151,13 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2)
static final long REQUEST_LISTENING_MUST_MATCH_PACKAGE = 172251878L;
+ /**
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ static final long REQUEST_LISTENING_OTHER_USER_NOOP = 242194868L;
+
private final Context mContext;
private final Handler mHandler = new Handler();
@@ -1888,7 +1895,12 @@
// Check current user
if (userId != currentUser) {
- throw new IllegalArgumentException("User " + userId + " is not the current user.");
+ if (CompatChanges.isChangeEnabled(REQUEST_LISTENING_OTHER_USER_NOOP, callingUid)) {
+ return;
+ } else {
+ throw new IllegalArgumentException(
+ "User " + userId + " is not the current user.");
+ }
}
}
if (mBar != null) {
diff --git a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
index de631bb..e7c073c 100644
--- a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
@@ -30,7 +30,6 @@
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.os.Binder;
import android.os.Handler;
@@ -140,20 +139,12 @@
/** Initialize the receivers and initiate the first NTP request */
public void systemRunning() {
// Listen for scheduled refreshes.
- mContext.registerReceiver(
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- onPollNetworkTime("scheduled refresh");
- }
- },
- new IntentFilter(ACTION_POLL),
- /*broadcastPermission=*/ null,
- mHandler);
+ ScheduledRefreshBroadcastReceiver receiver = new ScheduledRefreshBroadcastReceiver();
+ mContext.registerReceiver(receiver, new IntentFilter(ACTION_POLL));
// Listen for network connectivity changes.
- NetworkTimeUpdateCallback networkTimeUpdateCallback = new NetworkTimeUpdateCallback();
- mCM.registerDefaultNetworkCallback(networkTimeUpdateCallback, mHandler);
+ NetworkConnectivityCallback networkConnectivityCallback = new NetworkConnectivityCallback();
+ mCM.registerDefaultNetworkCallback(networkConnectivityCallback, mHandler);
// Listen for user settings changes.
ContentResolver resolver = mContext.getContentResolver();
@@ -223,8 +214,25 @@
}
}
+ private class ScheduledRefreshBroadcastReceiver extends BroadcastReceiver implements Runnable {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // The BroadcastReceiver has to complete quickly or an ANR will be triggered by the
+ // platform regardless of the receiver thread used. Instead of blocking the receiver
+ // thread, the long-running / blocking work is posted to mHandler to allow onReceive()
+ // to return immediately.
+ mHandler.post(this);
+ }
+
+ @Override
+ public void run() {
+ onPollNetworkTime("scheduled refresh");
+ }
+ }
+
// All callbacks will be invoked using mHandler because of how the callback is registered.
- private class NetworkTimeUpdateCallback extends NetworkCallback {
+ private class NetworkConnectivityCallback extends ConnectivityManager.NetworkCallback {
@Override
public void onAvailable(@NonNull Network network) {
Log.d(TAG, String.format("New default network %s; checking time.", network));
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 404ca01..c6684df 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -2125,6 +2125,26 @@
}
@Override
+ public void notifyTvMessage(IBinder sessionToken, String type, Bundle data, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "timeShiftEnablePositionTracking");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .notifyTvMessage(type, data);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in timeShiftEnablePositionTracking", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void startRecording(IBinder sessionToken, @Nullable Uri programUri,
@Nullable Bundle params, int userId) {
final int callingUid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 9aa7e56..fa3a186 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -702,8 +702,8 @@
+ AppTransition.appTransitionOldToString(transition)
+ " displayId: " + displayId);
}
- final boolean magnifying = mMagnifedViewport.isMagnifying();
- if (magnifying) {
+ final boolean isMagnifierActivated = isForceShowingMagnifiableBounds();
+ if (isMagnifierActivated) {
switch (transition) {
case WindowManager.TRANSIT_OLD_ACTIVITY_OPEN:
case WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN:
@@ -727,8 +727,8 @@
Slog.i(LOG_TAG, "Window transition: " + WindowManager.transitTypeToString(type)
+ " displayId: " + displayId);
}
- final boolean magnifying = mMagnifedViewport.isMagnifying();
- if (magnifying) {
+ final boolean isMagnifierActivated = isForceShowingMagnifiableBounds();
+ if (isMagnifierActivated) {
// All opening/closing situations.
switch (type) {
case WindowManager.TRANSIT_OPEN:
@@ -751,12 +751,12 @@
+ AppTransition.appTransitionOldToString(transition)
+ " displayId: " + windowState.getDisplayId());
}
- final boolean magnifying = mMagnifedViewport.isMagnifying();
+ final boolean isMagnifierActivated = isForceShowingMagnifiableBounds();
final int type = windowState.mAttrs.type;
switch (transition) {
case WindowManagerPolicy.TRANSIT_ENTER:
case WindowManagerPolicy.TRANSIT_SHOW: {
- if (!magnifying) {
+ if (!isMagnifierActivated) {
break;
}
switch (type) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 37e4890..28b974c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -573,6 +573,8 @@
Drawable mEnterpriseThumbnailDrawable;
+ boolean mPauseSchedulePendingForPip = false;
+
private void updateEnterpriseThumbnailDrawable(Context context) {
DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
mEnterpriseThumbnailDrawable = dpm.getResources().getDrawable(
@@ -1502,6 +1504,12 @@
mLastReportedMultiWindowMode = inPictureInPictureMode;
ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
true /* ignoreVisibility */);
+ if (inPictureInPictureMode && findMainWindow() == null) {
+ // Prevent malicious app entering PiP without valid WindowState, which can in turn
+ // result a non-touchable PiP window since the InputConsumer for PiP requires it.
+ EventLog.writeEvent(0x534e4554, "265293293", -1, "");
+ removeImmediately();
+ }
}
}
@@ -8118,9 +8126,7 @@
// The smallest screen width is the short side of screen bounds. Because the bounds
// and density won't be changed, smallestScreenWidthDp is also fixed.
overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp;
- // TODO(b/264276741): Check whether the runtime orietnation request is fixed rather than
- // the manifest orientation which may be obsolete.
- if (info.isFixedOrientation()) {
+ if (ActivityInfo.isFixedOrientation(getOverrideOrientation())) {
// lock rotation too. When in size-compat, onConfigurationChanged will watch for and
// apply runtime rotation changes.
overrideConfig.windowConfiguration.setRotation(
@@ -8145,7 +8151,13 @@
}
// Clear config override in #updateCompatDisplayInsets().
- onRequestedOverrideConfigurationChanged(EMPTY);
+ final int activityType = getActivityType();
+ final Configuration overrideConfig = getRequestedOverrideConfiguration();
+ overrideConfig.unset();
+ // Keep the activity type which was set when attaching to a task to prevent leaving it
+ // undefined.
+ overrideConfig.windowConfiguration.setActivityType(activityType);
+ onRequestedOverrideConfigurationChanged(overrideConfig);
}
@Override
@@ -8208,9 +8220,9 @@
if (isFixedOrientationLetterboxAllowed) {
resolveFixedOrientationConfiguration(newParentConfiguration);
}
-
- if (getCompatDisplayInsets() != null) {
- resolveSizeCompatModeConfiguration(newParentConfiguration);
+ final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();
+ if (compatDisplayInsets != null) {
+ resolveSizeCompatModeConfiguration(newParentConfiguration, compatDisplayInsets);
} else if (inMultiWindowMode() && !isFixedOrientationLetterboxAllowed) {
// We ignore activities' requested orientation in multi-window modes. They may be
// taken into consideration in resolveFixedOrientationConfiguration call above.
@@ -8227,7 +8239,7 @@
resolveAspectRatioRestriction(newParentConfiguration);
}
- if (isFixedOrientationLetterboxAllowed || getCompatDisplayInsets() != null
+ if (isFixedOrientationLetterboxAllowed || compatDisplayInsets != null
// In fullscreen, can be letterboxed for aspect ratio.
|| !inMultiWindowMode()) {
updateResolvedBoundsPosition(newParentConfiguration);
@@ -8235,7 +8247,7 @@
boolean isIgnoreOrientationRequest = mDisplayContent != null
&& mDisplayContent.getIgnoreOrientationRequest();
- if (getCompatDisplayInsets() == null
+ if (compatDisplayInsets == null
// for size compat mode set in updateCompatDisplayInsets
// Fixed orientation letterboxing is possible on both large screen devices
// with ignoreOrientationRequest enabled and on phones in split screen even with
@@ -8282,7 +8294,7 @@
info.neverSandboxDisplayApis(sConstrainDisplayApisConfig),
info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig),
!matchParentBounds(),
- getCompatDisplayInsets() != null,
+ compatDisplayInsets != null,
shouldCreateCompatDisplayInsets());
}
resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
@@ -8294,7 +8306,7 @@
/**
* @return The orientation to use to understand if reachability is enabled.
*/
- @ActivityInfo.ScreenOrientation
+ @Configuration.Orientation
int getOrientationForReachability() {
return mLetterboxUiController.hasInheritedLetterboxBehavior()
? mLetterboxUiController.getInheritedOrientation()
@@ -8698,7 +8710,8 @@
* Resolves consistent screen configuration for orientation and rotation changes without
* inheriting the parent bounds.
*/
- private void resolveSizeCompatModeConfiguration(Configuration newParentConfiguration) {
+ private void resolveSizeCompatModeConfiguration(Configuration newParentConfiguration,
+ @NonNull CompatDisplayInsets compatDisplayInsets) {
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
@@ -8719,13 +8732,13 @@
? requestedOrientation
// We should use the original orientation of the activity when possible to avoid
// forcing the activity in the opposite orientation.
- : getCompatDisplayInsets().mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
- ? getCompatDisplayInsets().mOriginalRequestedOrientation
+ : compatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
+ ? compatDisplayInsets.mOriginalRequestedOrientation
: newParentConfiguration.orientation;
int rotation = newParentConfiguration.windowConfiguration.getRotation();
final boolean isFixedToUserRotation = mDisplayContent == null
|| mDisplayContent.getDisplayRotation().isFixedToUserRotation();
- if (!isFixedToUserRotation && !getCompatDisplayInsets().mIsFloating) {
+ if (!isFixedToUserRotation && !compatDisplayInsets.mIsFloating) {
// Use parent rotation because the original display can be rotated.
resolvedConfig.windowConfiguration.setRotation(rotation);
} else {
@@ -8741,11 +8754,11 @@
// rely on them to contain the original and unchanging width and height of the app.
final Rect containingAppBounds = new Rect();
final Rect containingBounds = mTmpBounds;
- getCompatDisplayInsets().getContainerBounds(containingAppBounds, containingBounds, rotation,
+ compatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation,
orientation, orientationRequested, isFixedToUserRotation);
resolvedBounds.set(containingBounds);
// The size of floating task is fixed (only swap), so the aspect ratio is already correct.
- if (!getCompatDisplayInsets().mIsFloating) {
+ if (!compatDisplayInsets.mIsFloating) {
mIsAspectRatioApplied =
applyAspectRatio(resolvedBounds, containingAppBounds, containingBounds);
}
@@ -8754,7 +8767,7 @@
// are calculated in compat container space. The actual position on screen will be applied
// later, so the calculation is simpler that doesn't need to involve offset from parent.
getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
- getCompatDisplayInsets());
+ compatDisplayInsets);
// Use current screen layout as source because the size of app is independent to parent.
resolvedConfig.screenLayout = computeScreenLayout(
getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 88d1086..7024886 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -172,6 +172,12 @@
private static final int INVALID_LAUNCH_MODE = -1;
/**
+ * Avoid problematical apps from occupying system resources (e.g. the amount of surface) by
+ * launching too many activities in a task.
+ */
+ private static final long MAX_TASK_WEIGHT_FOR_ADDING_ACTIVITY = 300;
+
+ /**
* Feature flag to protect PendingIntent being abused to start background activity.
*/
@ChangeId
@@ -1647,6 +1653,13 @@
}
if (targetTask != null) {
+ if (targetTask.getTreeWeight() > MAX_TASK_WEIGHT_FOR_ADDING_ACTIVITY) {
+ Slog.e(TAG, "Remove " + targetTask + " because it has contained too many"
+ + " activities or windows (abort starting " + r
+ + " from uid=" + mCallingUid);
+ targetTask.removeImmediately("bulky-task");
+ return START_ABORTED;
+ }
mPriorAboveTask = TaskDisplayArea.getRootTaskAbove(targetTask.getRootTask());
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 2bd9052..5ea2854 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -473,9 +473,10 @@
/** Writes current activity states to the proto stream. */
public abstract void writeActivitiesToProto(ProtoOutputStream proto);
- /** Dump the current state based on the command. */
+ /** Dump the current state based on the command and filters. */
public abstract void dump(String cmd, FileDescriptor fd, PrintWriter pw, String[] args,
- int opti, boolean dumpAll, boolean dumpClient, String dumpPackage);
+ int opti, boolean dumpAll, boolean dumpClient, String dumpPackage,
+ int displayIdFilter);
/** Dump the current state for inclusion in process dump. */
public abstract boolean dumpForProcesses(FileDescriptor fd, PrintWriter pw, boolean dumpAll,
@@ -489,7 +490,8 @@
/** Dump the current activities state. */
public abstract boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name,
String[] args, int opti, boolean dumpAll, boolean dumpVisibleRootTasksOnly,
- boolean dumpFocusedRootTaskOnly, @UserIdInt int userId);
+ boolean dumpFocusedRootTaskOnly, boolean dumpVerbose, int displayIdFilter,
+ @UserIdInt int userId);
/** Dump the current state for inclusion in oom dump. */
public abstract void dumpForOom(PrintWriter pw);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 923ca79..898b477 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -229,6 +229,7 @@
import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
+import android.view.Display;
import android.view.IRecentsAnimationRunner;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
@@ -1495,7 +1496,7 @@
a.persistableMode = ActivityInfo.PERSIST_NEVER;
a.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
- a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS | ActivityInfo.FLAG_NO_HISTORY;
+ a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
a.resizeMode = RESIZE_MODE_UNRESIZEABLE;
a.configChanges = 0xffffffff;
@@ -3625,7 +3626,7 @@
null /* launchIntoPipHostActivity */, "enterPictureInPictureMode",
transition);
// Continue the pausing process after entering pip.
- if (r.isState(PAUSING)) {
+ if (r.isState(PAUSING) && r.mPauseSchedulePendingForPip) {
r.getTask().schedulePauseActivity(r, false /* userLeaving */,
false /* pauseImmediately */, true /* autoEnteringPip */, "auto-pip");
}
@@ -4103,42 +4104,50 @@
}
}
- void dumpVisibleActivitiesLocked(PrintWriter pw) {
+ void dumpVisibleActivitiesLocked(PrintWriter pw, int displayIdFilter) {
pw.println("ACTIVITY MANAGER VISIBLE ACTIVITIES (dumpsys activity visible)");
ArrayList<ActivityRecord> activities =
mRootWindowContainer.getDumpActivities("all", /* dumpVisibleRootTasksOnly */ true,
/* dumpFocusedRootTaskOnly */ false, UserHandle.USER_ALL);
boolean needSeparator = false;
+ boolean printedAnything = false;
for (int i = activities.size() - 1; i >= 0; i--) {
ActivityRecord activity = activities.get(i);
- if (!activity.isVisible()) {
+ if (!activity.isVisible() || (displayIdFilter != INVALID_DISPLAY
+ && activity.getDisplayId() != displayIdFilter)) {
continue;
}
if (needSeparator) {
pw.println();
}
+ printedAnything = true;
activity.dump(pw, "", true);
needSeparator = true;
}
+ if (!printedAnything) {
+ pw.println("(nothing)");
+ }
}
void dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) {
- dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage,
+ int opti, boolean dumpAll, boolean dumpClient, String dumpPackage,
+ int displayIdFilter) {
+ dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage, displayIdFilter,
"ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)");
}
void dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- int opti, boolean dumpAll, boolean dumpClient, String dumpPackage, String header) {
+ int opti, boolean dumpAll, boolean dumpClient, String dumpPackage, int displayIdFilter,
+ String header) {
pw.println(header);
boolean printedAnything = mRootWindowContainer.dumpActivities(fd, pw, dumpAll, dumpClient,
- dumpPackage);
+ dumpPackage, displayIdFilter);
boolean needSep = printedAnything;
boolean printed = ActivityTaskSupervisor.printThisActivity(pw,
- mRootWindowContainer.getTopResumedActivity(), dumpPackage, needSep,
- " ResumedActivity: ", null);
+ mRootWindowContainer.getTopResumedActivity(), dumpPackage, displayIdFilter, needSep,
+ " ResumedActivity: ", /* header= */ null);
if (printed) {
printedAnything = true;
needSep = false;
@@ -4196,7 +4205,8 @@
*/
protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args,
int opti, boolean dumpAll, boolean dumpVisibleRootTasksOnly,
- boolean dumpFocusedRootTaskOnly, @UserIdInt int userId) {
+ boolean dumpFocusedRootTaskOnly, boolean dumpVerbose, int displayIdFilter,
+ @UserIdInt int userId) {
ArrayList<ActivityRecord> activities;
synchronized (mGlobalLock) {
@@ -4213,6 +4223,7 @@
Task lastTask = null;
boolean needSep = false;
+ boolean printedAnything = false;
for (int i = activities.size() - 1; i >= 0; i--) {
ActivityRecord r = activities.get(i);
if (needSep) {
@@ -4220,21 +4231,31 @@
}
needSep = true;
synchronized (mGlobalLock) {
- final Task task = r.getTask();
+ Task task = r.getTask();
+ int displayId = task.getDisplayId();
+ if (displayIdFilter != INVALID_DISPLAY && displayId != displayIdFilter) {
+ continue;
+ }
if (lastTask != task) {
+ printedAnything = true;
lastTask = task;
pw.print("TASK ");
pw.print(lastTask.affinity);
pw.print(" id=");
pw.print(lastTask.mTaskId);
pw.print(" userId=");
- pw.println(lastTask.mUserId);
+ pw.print(lastTask.mUserId);
+ printDisplayInfoAndNewLine(pw, r);
if (dumpAll) {
lastTask.dump(pw, " ");
}
}
}
- dumpActivity(" ", fd, pw, activities.get(i), newArgs, dumpAll);
+ dumpActivity(" ", fd, pw, activities.get(i), newArgs, dumpAll, dumpVerbose);
+ }
+ if (!printedAnything) {
+ // Typically happpens when no task matches displayIdFilter
+ pw.println("(nothing)");
}
return true;
}
@@ -4244,7 +4265,7 @@
* there is a thread associated with the activity.
*/
private void dumpActivity(String prefix, FileDescriptor fd, PrintWriter pw,
- final ActivityRecord r, String[] args, boolean dumpAll) {
+ ActivityRecord r, String[] args, boolean dumpAll, boolean dumpVerbose) {
String innerPrefix = prefix + " ";
IApplicationThread appThread = null;
synchronized (mGlobalLock) {
@@ -4255,13 +4276,22 @@
pw.print(Integer.toHexString(System.identityHashCode(r)));
pw.print(" pid=");
if (r.hasProcess()) {
- pw.println(r.app.getPid());
+ pw.print(r.app.getPid());
appThread = r.app.getThread();
} else {
- pw.println("(not running)");
+ pw.print("(not running)");
+ }
+ if (dumpVerbose) {
+ pw.print(" userId=");
+ pw.print(r.mUserId);
+ pw.print(" uid=");
+ pw.print(r.getUid());
+ printDisplayInfoAndNewLine(pw, r);
+ } else {
+ pw.println();
}
if (dumpAll) {
- r.dump(pw, innerPrefix, true /* dumpAll */);
+ r.dump(pw, innerPrefix, /* dumpAll= */ true);
}
}
if (appThread != null) {
@@ -4279,6 +4309,20 @@
}
}
+ private void printDisplayInfoAndNewLine(PrintWriter pw, ActivityRecord r) {
+ pw.print(" displayId=");
+ DisplayContent displayContent = r.getDisplayContent();
+ if (displayContent == null) {
+ pw.println("N/A");
+ return;
+ }
+ Display display = displayContent.getDisplay();
+ pw.print(display.getDisplayId());
+ pw.print("(type=");
+ pw.print(Display.typeToString(display.getType()));
+ pw.println(")");
+ }
+
private void writeSleepStateToProto(ProtoOutputStream proto, int wakeFullness,
boolean testPssMode) {
final long sleepToken = proto.start(ActivityManagerServiceDumpProcessesProto.SLEEP_STATUS);
@@ -5308,7 +5352,7 @@
+ "------------");
dumpActivitiesLocked(null /* fd */, pw, null /* args */, 0 /* opti */,
true /* dumpAll */, false /* dumpClient */, null /* dumpPackage */,
- "" /* header */);
+ INVALID_DISPLAY, "" /* header */);
pw.println();
pw.close();
@@ -6359,10 +6403,11 @@
@Override
public void dump(String cmd, FileDescriptor fd, PrintWriter pw, String[] args, int opti,
- boolean dumpAll, boolean dumpClient, String dumpPackage) {
+ boolean dumpAll, boolean dumpClient, String dumpPackage, int displayIdFilter) {
synchronized (mGlobalLock) {
if (DUMP_ACTIVITIES_CMD.equals(cmd) || DUMP_ACTIVITIES_SHORT_CMD.equals(cmd)) {
- dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
+ dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage,
+ displayIdFilter);
} else if (DUMP_LASTANR_CMD.equals(cmd)) {
dumpLastANRLocked(pw);
} else if (DUMP_LASTANR_TRACES_CMD.equals(cmd)) {
@@ -6378,7 +6423,7 @@
} else if (DUMP_TOP_RESUMED_ACTIVITY.equals(cmd)) {
dumpTopResumedActivityLocked(pw);
} else if (DUMP_VISIBLE_ACTIVITIES.equals(cmd)) {
- dumpVisibleActivitiesLocked(pw);
+ dumpVisibleActivitiesLocked(pw, displayIdFilter);
}
}
}
@@ -6573,9 +6618,11 @@
@Override
public boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name,
String[] args, int opti, boolean dumpAll, boolean dumpVisibleRootTasksOnly,
- boolean dumpFocusedRootTaskOnly, @UserIdInt int userId) {
+ boolean dumpFocusedRootTaskOnly, boolean dumpVerbose, int displayIdFilter,
+ @UserIdInt int userId) {
return ActivityTaskManagerService.this.dumpActivity(fd, pw, name, args, opti, dumpAll,
- dumpVisibleRootTasksOnly, dumpFocusedRootTaskOnly, userId);
+ dumpVisibleRootTasksOnly, dumpFocusedRootTaskOnly, dumpVerbose, displayIdFilter,
+ userId);
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 710c4af..a0a2557 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2218,7 +2218,14 @@
static boolean printThisActivity(PrintWriter pw, ActivityRecord activity, String dumpPackage,
boolean needSep, String prefix, Runnable header) {
- if (activity != null) {
+ return printThisActivity(pw, activity, dumpPackage, INVALID_DISPLAY, needSep, prefix,
+ header);
+ }
+
+ static boolean printThisActivity(PrintWriter pw, ActivityRecord activity, String dumpPackage,
+ int displayIdFilter, boolean needSep, String prefix, Runnable header) {
+ if (activity != null && (displayIdFilter == INVALID_DISPLAY
+ || displayIdFilter == activity.getDisplayId())) {
if (dumpPackage == null || dumpPackage.equals(activity.packageName)) {
if (needSep) {
pw.println();
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 2d45dc2..f9f972c 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -21,6 +21,7 @@
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_NONE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
@@ -31,6 +32,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.res.ResourceId;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
@@ -51,12 +53,14 @@
import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.LocalServices;
import com.android.server.wm.utils.InsetUtils;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Objects;
import java.util.function.Consumer;
/**
@@ -80,6 +84,8 @@
// re-parenting leashes and set launch behind, etc. Will be handled when transition finished.
private AnimationHandler.ScheduleAnimationBuilder mPendingAnimationBuilder;
+ private static int sDefaultAnimationResId;
+
/**
* true if the back predictability feature is enabled
*/
@@ -255,9 +261,19 @@
} else if (prevActivity != null) {
if (!isOccluded || prevActivity.canShowWhenLocked()) {
// We have another Activity in the same currentTask to go to
- backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
+ final WindowContainer parent = currentActivity.getParent();
+ final boolean isCustomize = parent != null
+ && (parent.asTask() != null
+ || (parent.asTaskFragment() != null
+ && parent.canCustomizeAppTransition()))
+ && isCustomizeExitAnimation(window);
+ if (isCustomize) {
+ infoBuilder.setWindowAnimations(
+ window.mAttrs.packageName, window.mAttrs.windowAnimations);
+ }
removedWindowContainer = currentActivity;
prevTask = prevActivity.getTask();
+ backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
} else {
backType = BackNavigationInfo.TYPE_CALLBACK;
}
@@ -370,6 +386,37 @@
return kc.isKeyguardLocked(displayId) && kc.isDisplayOccluded(displayId);
}
+ /**
+ * There are two ways to customize activity exit animation, one is to provide the
+ * windowAnimationStyle by Activity#setTheme, another one is to set resId by
+ * Window#setWindowAnimations.
+ * Not all run-time customization methods can be checked from here, such as
+ * overridePendingTransition, which the animation resource will be set just before the
+ * transition is about to happen.
+ */
+ private static boolean isCustomizeExitAnimation(WindowState window) {
+ // The default animation ResId is loaded from system package, so the result must match.
+ if (Objects.equals(window.mAttrs.packageName, "android")) {
+ return false;
+ }
+ if (window.mAttrs.windowAnimations != 0) {
+ final TransitionAnimation transitionAnimation = window.getDisplayContent()
+ .mAppTransition.mTransitionAnimation;
+ final int attr = com.android.internal.R.styleable
+ .WindowAnimation_activityCloseExitAnimation;
+ final int appResId = transitionAnimation.getAnimationResId(
+ window.mAttrs, attr, TRANSIT_OLD_NONE);
+ if (ResourceId.isValid(appResId)) {
+ if (sDefaultAnimationResId == 0) {
+ sDefaultAnimationResId = transitionAnimation.getDefaultAnimationResId(attr,
+ TRANSIT_OLD_NONE);
+ }
+ return sDefaultAnimationResId != appResId;
+ }
+ }
+ return false;
+ }
+
// For legacy transition.
/**
* Once we find the transition targets match back animation targets, remove the target from
@@ -997,7 +1044,7 @@
return () -> {
try {
- mBackAnimationAdapter.getRunner().onAnimationStart(mType,
+ mBackAnimationAdapter.getRunner().onAnimationStart(
targets, null, null, callback);
} catch (RemoteException e) {
e.printStackTrace();
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index ce3379e..389c908 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -20,11 +20,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.Display.TYPE_INTERNAL;
import static android.view.InsetsFrameProvider.SOURCE_FRAME;
-import static android.view.InsetsState.ITYPE_CAPTION_BAR;
-import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -198,6 +193,11 @@
private boolean mCanSystemBarsBeShownByUser;
+ /**
+ * Let remote insets controller control system bars regardless of other settings.
+ */
+ private boolean mRemoteInsetsControllerControlsSystemBars;
+
StatusBarManagerInternal getStatusBarManagerInternal() {
synchronized (mServiceAcquireLock) {
if (mStatusBarManagerInternal == null) {
@@ -781,6 +781,17 @@
return mScreenOnListener;
}
+
+ boolean isRemoteInsetsControllerControllingSystemBars() {
+ return mRemoteInsetsControllerControlsSystemBars;
+ }
+
+ @VisibleForTesting
+ void setRemoteInsetsControllerControlsSystemBars(
+ boolean remoteInsetsControllerControlsSystemBars) {
+ mRemoteInsetsControllerControlsSystemBars = remoteInsetsControllerControlsSystemBars;
+ }
+
public void screenTurnedOn(ScreenOnListener screenOnListener) {
synchronized (mLock) {
mScreenOnEarly = true;
@@ -1029,7 +1040,6 @@
android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
"DisplayPolicy");
}
- enforceSingleInsetsTypeCorrespondingToWindowType(attrs.providedInsets);
}
return ADD_OKAY;
}
@@ -1061,7 +1071,7 @@
final TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider =
getFrameProvider(win, provider, i);
final InsetsFrameProvider.InsetsSizeOverride[] overrides =
- provider.insetsSizeOverrides;
+ provider.getInsetsSizeOverrides();
final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>>
overrideProviders;
if (overrides != null) {
@@ -1070,19 +1080,14 @@
final TriConsumer<DisplayFrames, WindowContainer, Rect>
overrideFrameProvider =
getOverrideFrameProvider(win, i, j);
- overrideProviders.put(overrides[j].windowType, overrideFrameProvider);
+ overrideProviders.put(overrides[j].getWindowType(), overrideFrameProvider);
}
} else {
overrideProviders = null;
}
- // TODO (b/234093736): Let InsetsFrameProvider have the following fields:
- // - IBinder owner.
- // - int index.
- // - @InsetsType int type.
- // So we can create the id by using InsetsSource#createId.
- // And we won't need toPublicType anymore.
- final int id = provider.type;
- final @InsetsType int type = InsetsState.toPublicType(id);
+ final @InsetsType int type = provider.getType();
+ final int id = InsetsSource.createId(
+ provider.getOwner(), provider.getIndex(), type);
mDisplayContent.getInsetsStateController().getOrCreateSourceProvider(id, type)
.setWindowContainer(win, frameProvider, overrideProviders);
mInsetsSourceWindowsExceptIme.add(win);
@@ -1093,7 +1098,7 @@
@Nullable
private TriConsumer<DisplayFrames, WindowContainer, Rect> getFrameProvider(WindowState win,
InsetsFrameProvider provider, int index) {
- if (provider.insetsSize == null && provider.source == SOURCE_FRAME) {
+ if (provider.getInsetsSize() == null && provider.getSource() == SOURCE_FRAME) {
return null;
}
return (displayFrames, windowContainer, inOutFrame) -> {
@@ -1101,8 +1106,8 @@
final InsetsFrameProvider ifp = lp.providedInsets[index];
InsetsFrameProvider.calculateInsetsFrame(displayFrames.mUnrestricted,
windowContainer.getBounds(), displayFrames.mDisplayCutoutSafe, inOutFrame,
- ifp.source, ifp.insetsSize, lp.privateFlags,
- ifp.minimalInsetsSizeInDisplayCutoutSafe);
+ ifp.getSource(), ifp.getInsetsSize(), lp.privateFlags,
+ ifp.getMinimalInsetsSizeInDisplayCutoutSafe());
};
}
@@ -1114,8 +1119,8 @@
final InsetsFrameProvider ifp = lp.providedInsets[index];
InsetsFrameProvider.calculateInsetsFrame(displayFrames.mUnrestricted,
windowContainer.getBounds(), displayFrames.mDisplayCutoutSafe, inOutFrame,
- ifp.source, ifp.insetsSizeOverrides[overrideIndex].insetsSize, lp.privateFlags,
- null);
+ ifp.getSource(), ifp.getInsetsSizeOverrides()[overrideIndex].getInsetsSize(),
+ lp.privateFlags, null /* displayCutoutSafeInsetsSize */);
};
}
@@ -1141,24 +1146,6 @@
};
}
- private static void enforceSingleInsetsTypeCorrespondingToWindowType(
- InsetsFrameProvider[] providers) {
- int count = 0;
- for (InsetsFrameProvider provider : providers) {
- switch (provider.type) {
- case ITYPE_NAVIGATION_BAR:
- case ITYPE_STATUS_BAR:
- case ITYPE_CLIMATE_BAR:
- case ITYPE_EXTRA_NAVIGATION_BAR:
- case ITYPE_CAPTION_BAR:
- if (++count > 1) {
- throw new IllegalArgumentException(
- "Multiple InsetsTypes corresponding to Window type");
- }
- }
- }
- }
-
/**
* Called when a window is being removed from a window manager. Must not
* throw an exception -- clean up as much as possible.
@@ -1664,6 +1651,8 @@
mRightGestureInset = mGestureNavigationSettingsObserver.getRightSensitivity(res);
mNavigationBarAlwaysShowOnSideGesture =
res.getBoolean(R.bool.config_navBarAlwaysShowOnSideEdgeGesture);
+ mRemoteInsetsControllerControlsSystemBars = res.getBoolean(
+ R.bool.config_remoteInsetsControllerControlsSystemBars);
updateConfigurationAndScreenSizeDependentBehaviors();
@@ -2553,7 +2542,7 @@
pw.print(mForceShowNavigationBarEnabled);
pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars=");
- pw.println(mDisplayContent.getInsetsPolicy().getRemoteInsetsControllerControlsSystemBars());
+ pw.println(mRemoteInsetsControllerControlsSystemBars);
pw.print(prefix); pw.println("mDecorInsetsInfo:");
for (int rotation = 0; rotation < mDecorInsets.mInfoForRotation.length; rotation++) {
final DecorInsets.Info info = mDecorInsets.mInfoForRotation[rotation];
@@ -2662,10 +2651,9 @@
*/
private static boolean intersectsAnyInsets(Rect bounds, InsetsState insetsState,
@InsetsType int insetsType) {
- final ArraySet<Integer> internalTypes = InsetsState.toInternalType(insetsType);
- for (int i = 0; i < internalTypes.size(); i++) {
- final InsetsSource source = insetsState.peekSource(internalTypes.valueAt(i));
- if (source == null || !source.isVisible()) {
+ for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
+ final InsetsSource source = insetsState.sourceAt(i);
+ if ((source.getType() & insetsType) == 0 || !source.isVisible()) {
continue;
}
if (Rect.intersects(bounds, source.getFrame())) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index c1f2b2b..868a15d 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -116,11 +116,6 @@
private @InsetsType int mShowingTransientTypes;
private boolean mAnimatingShown;
- /**
- * Let remote insets controller control system bars regardless of other settings.
- */
- private boolean mRemoteInsetsControllerControlsSystemBars;
-
private final boolean mHideNavBarForKeyboard;
private final float[] mTmpFloat9 = new float[9];
@@ -129,22 +124,9 @@
mDisplayContent = displayContent;
mPolicy = displayContent.getDisplayPolicy();
final Resources r = mPolicy.getContext().getResources();
- mRemoteInsetsControllerControlsSystemBars = r.getBoolean(
- R.bool.config_remoteInsetsControllerControlsSystemBars);
mHideNavBarForKeyboard = r.getBoolean(R.bool.config_hideNavBarForKeyboard);
}
- boolean getRemoteInsetsControllerControlsSystemBars() {
- return mRemoteInsetsControllerControlsSystemBars;
- }
-
- /**
- * Used only for testing.
- */
- @VisibleForTesting
- void setRemoteInsetsControllerControlsSystemBars(boolean controlsSystemBars) {
- mRemoteInsetsControllerControlsSystemBars = controlsSystemBars;
- }
/** Updates the target which can control system bars. */
void updateBarControlTarget(@Nullable WindowState focusedWin) {
@@ -329,18 +311,16 @@
state.removeSource(ID_IME);
} else if (attrs.providedInsets != null) {
for (InsetsFrameProvider provider : attrs.providedInsets) {
- // TODO(b/234093736): Let InsetsFrameProvider return the public type and the ID.
- final int sourceId = provider.type;
- final @InsetsType int type = sourceId == ID_IME
- ? WindowInsets.Type.ime()
- : InsetsState.toPublicType(sourceId);
+ final int id = InsetsSource.createId(
+ provider.getOwner(), provider.getIndex(), provider.getType());
+ final @InsetsType int type = provider.getType();
if ((type & WindowInsets.Type.systemBars()) == 0) {
continue;
}
if (state == originalState) {
state = new InsetsState(state);
}
- state.removeSource(sourceId);
+ state.removeSource(id);
}
}
@@ -632,7 +612,8 @@
if (focusedWin == null) {
return false;
}
- if (!mRemoteInsetsControllerControlsSystemBars) {
+
+ if (!mPolicy.isRemoteInsetsControllerControllingSystemBars()) {
return false;
}
if (mDisplayContent == null || mDisplayContent.mRemoteInsetsControlTarget == null) {
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 2b7a451..0953604 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -16,8 +16,6 @@
package com.android.server.wm;
-import static android.view.InsetsSource.ID_IME;
-
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_INSETS;
import static com.android.server.wm.InsetsSourceProviderProto.CAPTURED_LEASH;
import static com.android.server.wm.InsetsSourceProviderProto.CLIENT_VISIBLE;
@@ -41,7 +39,6 @@
import android.graphics.Rect;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
-import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.SurfaceControl;
@@ -514,33 +511,13 @@
}
protected void updateVisibility() {
- mSource.setVisible(mServerVisible && (isMirroredSource() || mClientVisible));
+ mSource.setVisible(mServerVisible && mClientVisible);
ProtoLog.d(WM_DEBUG_WINDOW_INSETS,
"InsetsSource updateVisibility for %s, serverVisible: %s clientVisible: %s",
WindowInsets.Type.toString(mSource.getType()),
mServerVisible, mClientVisible);
}
- private boolean isMirroredSource() {
- if (mWindowContainer == null) {
- return false;
- }
- if (mWindowContainer.asWindowState() == null) {
- return false;
- }
- final InsetsFrameProvider[] providers =
- ((WindowState) mWindowContainer).mAttrs.providedInsets;
- if (providers == null) {
- return false;
- }
- for (int i = 0; i < providers.length; i++) {
- if (providers[i].type == ID_IME) {
- return true;
- }
- }
- return false;
- }
-
InsetsSourceControl getControl(InsetsControlTarget target) {
if (target == mControlTarget) {
if (!mIsLeashReadyForDispatching && mControl != null) {
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 0e1e63e..e4ffb8d 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -138,14 +138,6 @@
}
/**
- * @return The provider of a source ID or null if we don't have it.
- */
- @Nullable
- WindowContainerInsetsSourceProvider peekSourceProvider(int id) {
- return mProviders.get(id);
- }
-
- /**
* Called when a layout pass has occurred.
*/
void onPostLayout() {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 44b1cc8..7208934 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1427,7 +1427,7 @@
* the first opaque activity beneath.
*/
boolean hasInheritedLetterboxBehavior() {
- return mLetterboxConfigListener != null && !mActivityRecord.matchParentBounds();
+ return mLetterboxConfigListener != null;
}
/**
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index e147219..b2a4df1 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -3472,7 +3472,7 @@
}
boolean dumpActivities(FileDescriptor fd, PrintWriter pw, boolean dumpAll, boolean dumpClient,
- String dumpPackage) {
+ String dumpPackage, int displayIdFilter) {
boolean[] printed = {false};
boolean[] needSep = {false};
for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
@@ -3480,6 +3480,10 @@
if (printed[0]) {
pw.println();
}
+ if (displayIdFilter != Display.INVALID_DISPLAY
+ && displayContent.mDisplayId != displayIdFilter) {
+ continue;
+ }
pw.print("Display #");
pw.print(displayContent.mDisplayId);
pw.println(" (activities from top to bottom):");
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7433c7e..3680e6d 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3516,7 +3516,10 @@
info.isKeyguardOccluded =
mAtmService.mKeyguardController.isDisplayOccluded(DEFAULT_DISPLAY);
- info.startingWindowTypeParameter = activity.mStartingData.mTypeParams;
+ info.startingWindowTypeParameter = activity.mStartingData != null
+ ? activity.mStartingData.mTypeParams
+ : (StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED
+ | StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS);
if ((info.startingWindowTypeParameter
& StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED) != 0) {
final WindowState topMainWin = getWindow(w -> w.mAttrs.type == TYPE_BASE_APPLICATION);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 035859e..2ddb307 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1637,6 +1637,7 @@
if (prev.attachedToProcess()) {
if (shouldAutoPip) {
+ prev.mPauseSchedulePendingForPip = true;
boolean didAutoPip = mAtmService.enterPictureInPictureMode(
prev, prev.pictureInPictureArgs, false /* fromClient */);
ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, entering PIP mode "
@@ -1700,6 +1701,7 @@
boolean pauseImmediately, boolean autoEnteringPip, String reason) {
ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev);
try {
+ prev.mPauseSchedulePendingForPip = false;
EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),
prev.shortComponentName, "userLeaving=" + userLeaving, reason);
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 3a30e4b..b131365 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.TaskInfo.cameraCompatControlStateToString;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
@@ -43,6 +44,7 @@
import android.view.SurfaceControl;
import android.window.ITaskOrganizer;
import android.window.ITaskOrganizerController;
+import android.window.IWindowlessStartingSurfaceCallback;
import android.window.SplashScreenView;
import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
@@ -656,9 +658,10 @@
info.splashScreenThemeResId = launchTheme;
}
info.taskSnapshot = taskSnapshot;
+ info.appToken = activity.token;
// make this happen prior than prepare surface
try {
- lastOrganizer.addStartingWindow(info, activity.token);
+ lastOrganizer.addStartingWindow(info);
} catch (RemoteException e) {
Slog.e(TAG, "Exception sending onTaskStart callback", e);
return false;
@@ -704,6 +707,55 @@
}
}
+ /**
+ * Create a starting surface which attach on a given surface.
+ * @param activity Target activity, this isn't necessary to be the top activity.
+ * @param root The root surface which the created surface will attach on.
+ * @param taskSnapshot Whether to draw snapshot.
+ * @param callback Called when surface is drawn and attached to the root surface.
+ * @return The taskId, this is a token and should be used to remove the surface, even if
+ * the task was removed from hierarchy.
+ */
+ int addWindowlessStartingSurface(Task task, ActivityRecord activity, SurfaceControl root,
+ TaskSnapshot taskSnapshot, IWindowlessStartingSurfaceCallback callback) {
+ final Task rootTask = task.getRootTask();
+ if (rootTask == null) {
+ return INVALID_TASK_ID;
+ }
+ final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
+ if (lastOrganizer == null) {
+ return INVALID_TASK_ID;
+ }
+ final StartingWindowInfo info = task.getStartingWindowInfo(activity);
+ info.taskInfo.taskDescription = activity.taskDescription;
+ info.taskSnapshot = taskSnapshot;
+ info.windowlessStartingSurfaceCallback = callback;
+ info.rootSurface = root;
+ try {
+ lastOrganizer.addStartingWindow(info);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception sending addWindowlessStartingSurface ", e);
+ return INVALID_TASK_ID;
+ }
+ return task.mTaskId;
+ }
+
+ void removeWindowlessStartingSurface(int taskId, boolean immediately) {
+ final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
+ if (lastOrganizer == null || taskId == 0) {
+ return;
+ }
+ final StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo();
+ removalInfo.taskId = taskId;
+ removalInfo.windowlessSurface = true;
+ removalInfo.removeImmediately = immediately;
+ try {
+ lastOrganizer.removeStartingWindow(removalInfo);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception sending removeWindowlessStartingSurface ", e);
+ }
+ }
+
boolean copySplashScreenView(Task task) {
final Task rootTask = task.getRootTask();
if (rootTask == null) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index da7400c..132f5a7 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1473,7 +1473,7 @@
* {@link Configuration#ORIENTATION_PORTRAIT},
* {@link Configuration#ORIENTATION_UNDEFINED}).
*/
- @ScreenOrientation
+ @Configuration.Orientation
int getRequestedConfigurationOrientation() {
return getRequestedConfigurationOrientation(false /* forDisplay */);
}
@@ -1491,7 +1491,7 @@
* {@link Configuration#ORIENTATION_PORTRAIT},
* {@link Configuration#ORIENTATION_UNDEFINED}).
*/
- @ScreenOrientation
+ @Configuration.Orientation
int getRequestedConfigurationOrientation(boolean forDisplay) {
int requestedOrientation = getOverrideOrientation();
final RootDisplayArea root = getRootDisplayArea();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 98563f6..45cdacd 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2267,19 +2267,18 @@
if (win.mAttrs.providedInsets == null || attrs.providedInsets == null
|| (win.mAttrs.providedInsets.length != attrs.providedInsets.length)) {
throw new IllegalArgumentException(
- "Insets types can not be changed after the window is added.");
+ "Insets amount can not be changed after the window is added.");
} else {
final int insetsTypes = attrs.providedInsets.length;
for (int i = 0; i < insetsTypes; i++) {
- if (win.mAttrs.providedInsets[i].type != attrs.providedInsets[i].type) {
+ if (!win.mAttrs.providedInsets[i].idEquals(attrs.providedInsets[i])) {
throw new IllegalArgumentException(
- "Insets types can not be changed after the window is "
- + "added.");
+ "Insets ID can not be changed after the window is added.");
}
final InsetsFrameProvider.InsetsSizeOverride[] overrides =
- win.mAttrs.providedInsets[i].insetsSizeOverrides;
+ win.mAttrs.providedInsets[i].getInsetsSizeOverrides();
final InsetsFrameProvider.InsetsSizeOverride[] newOverrides =
- attrs.providedInsets[i].insetsSizeOverrides;
+ attrs.providedInsets[i].getInsetsSizeOverrides();
if (!(overrides == null && newOverrides == null)) {
if (overrides == null || newOverrides == null
|| (overrides.length != newOverrides.length)) {
@@ -2289,7 +2288,8 @@
} else {
final int overrideTypes = overrides.length;
for (int j = 0; j < overrideTypes; j++) {
- if (overrides[j].windowType != newOverrides[j].windowType) {
+ if (overrides[j].getWindowType()
+ != newOverrides[j].getWindowType()) {
throw new IllegalArgumentException(
"Insets override types can not be changed after"
+ " the window is added.");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 09f7fb6..cf0fc09 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2018,16 +2018,19 @@
/**
* Like isOnScreen(), but we don't return true if the window is part
- * of a transition that has not yet been started.
+ * of a transition but has not yet started animating.
*/
boolean isReadyForDisplay() {
- if (mToken.waitingToShow && getDisplayContent().mAppTransition.isTransitionSet()) {
+ if (!mHasSurface || mDestroying || !isVisibleByPolicy()) {
+ return false;
+ }
+ if (mToken.waitingToShow && getDisplayContent().mAppTransition.isTransitionSet()
+ && !isAnimating(TRANSITION | PARENTS, ANIMATION_TYPE_APP_TRANSITION)) {
return false;
}
final boolean parentAndClientVisible = !isParentWindowHidden()
&& mViewVisibility == View.VISIBLE && mToken.isVisible();
- return mHasSurface && isVisibleByPolicy() && !mDestroying
- && (parentAndClientVisible || isAnimating(TRANSITION | PARENTS));
+ return parentAndClientVisible || isAnimating(TRANSITION | PARENTS, ANIMATION_TYPE_ALL);
}
boolean isFullyTransparent() {
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index c8ec7c2..e84f0cc 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -20,17 +20,18 @@
import android.content.ComponentName;
import android.content.Context;
import android.credentials.ClearCredentialStateRequest;
+import android.credentials.CredentialProviderInfo;
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
-import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import com.android.server.credentials.metrics.ApiName;
import com.android.server.credentials.metrics.ApiStatus;
+import com.android.server.credentials.metrics.ProviderStatusForMetrics;
import java.util.ArrayList;
@@ -120,22 +121,22 @@
Log.i(TAG, "respondToClientWithResponseAndFinish");
if (isSessionCancelled()) {
mChosenProviderMetric.setChosenProviderStatus(
- MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_SUCCESS);
+ ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED);
+ ApiStatus.CLIENT_CANCELED);
finishSession(/*propagateCancellation=*/true);
return;
}
try {
mClientCallback.onSuccess();
logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_SUCCESS);
+ ApiStatus.SUCCESS);
} catch (RemoteException e) {
mChosenProviderMetric.setChosenProviderStatus(
- MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_FAILURE);
+ ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
Log.i(TAG, "Issue while propagating the response to the client");
logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_FAILURE);
+ ApiStatus.FAILURE);
}
finishSession(/*propagateCancellation=*/false);
}
@@ -144,7 +145,7 @@
Log.i(TAG, "respondToClientWithErrorAndFinish");
if (isSessionCancelled()) {
logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED);
+ ApiStatus.CLIENT_CANCELED);
finishSession(/*propagateCancellation=*/true);
return;
}
@@ -154,7 +155,7 @@
e.printStackTrace();
}
logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_FAILURE);
+ ApiStatus.FAILURE);
finishSession(/*propagateCancellation=*/false);
}
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 0c1133c..7e1780d 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -16,9 +16,6 @@
package com.android.server.credentials;
-import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_FAILURE;
-import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_SUCCESS;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
@@ -27,17 +24,18 @@
import android.credentials.CreateCredentialRequest;
import android.credentials.CreateCredentialResponse;
import android.credentials.CredentialManager;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ICreateCredentialCallback;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
-import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import com.android.server.credentials.metrics.ApiName;
import com.android.server.credentials.metrics.ApiStatus;
+import com.android.server.credentials.metrics.ProviderStatusForMetrics;
import java.util.ArrayList;
@@ -103,11 +101,11 @@
setChosenMetric(componentName);
if (response != null) {
mChosenProviderMetric.setChosenProviderStatus(
- METRICS_PROVIDER_STATUS_FINAL_SUCCESS);
+ ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
respondToClientWithResponseAndFinish(response);
} else {
mChosenProviderMetric.setChosenProviderStatus(
- METRICS_PROVIDER_STATUS_FINAL_FAILURE);
+ ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
"Invalid response");
}
@@ -144,18 +142,18 @@
}
if (isSessionCancelled()) {
logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED);
+ ApiStatus.CLIENT_CANCELED);
finishSession(/*propagateCancellation=*/true);
return;
}
try {
mClientCallback.onResponse(response);
logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_SUCCESS);
+ ApiStatus.SUCCESS);
} catch (RemoteException e) {
Log.i(TAG, "Issue while responding to client: " + e.getMessage());
logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_FAILURE);
+ ApiStatus.FAILURE);
}
finishSession(/*propagateCancellation=*/false);
}
@@ -168,7 +166,7 @@
}
if (isSessionCancelled()) {
logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED);
+ ApiStatus.CLIENT_CANCELED);
finishSession(/*propagateCancellation=*/true);
return;
}
@@ -184,10 +182,10 @@
private void logFailureOrUserCancel(String errorType) {
if (CreateCredentialException.TYPE_USER_CANCELED.equals(errorType)) {
logApiCall(ApiName.CREATE_CREDENTIAL,
- /* apiStatus */ ApiStatus.METRICS_API_STATUS_USER_CANCELED);
+ /* apiStatus */ ApiStatus.USER_CANCELED);
} else {
logApiCall(ApiName.CREATE_CREDENTIAL,
- /* apiStatus */ ApiStatus.METRICS_API_STATUS_FAILURE);
+ /* apiStatus */ ApiStatus.FAILURE);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
index d768d23..14c49b3 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
@@ -34,7 +34,7 @@
import java.util.concurrent.locks.ReentrantLock;
/** Contains information on what CredentialProvider has what provisioned Credential. */
-public final class CredentialDescriptionRegistry {
+public class CredentialDescriptionRegistry {
private static final int MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS = 128;
private static final int MAX_ALLOWED_ENTRIES_PER_PROVIDER = 16;
@@ -54,7 +54,8 @@
final String mFlattenedRequest;
final List<CredentialEntry> mCredentialEntries;
- private FilterResult(String packageName,
+ @VisibleForTesting
+ FilterResult(String packageName,
String flattenedRequest,
List<CredentialEntry> credentialEntries) {
mPackageName = packageName;
@@ -92,10 +93,10 @@
}
}
- /** Clears an existing session for a given user identifier. */
+ /** Clears an existing session for a given user identifier. Used when testing only. */
@GuardedBy("sLock")
@VisibleForTesting
- public static void clearAllSessions() {
+ static void clearAllSessions() {
sLock.lock();
try {
sCredentialDescriptionSessionPerUser.clear();
@@ -104,6 +105,19 @@
}
}
+ /** Sets an existing session for a given user identifier. Used when testing only. */
+ @GuardedBy("sLock")
+ @VisibleForTesting
+ static void setSession(int userId, CredentialDescriptionRegistry
+ credentialDescriptionRegistry) {
+ sLock.lock();
+ try {
+ sCredentialDescriptionSessionPerUser.put(userId, credentialDescriptionRegistry);
+ } finally {
+ sLock.unlock();
+ }
+ }
+
private Map<String, Set<CredentialDescription>> mCredentialDescriptions;
private int mTotalDescriptionCount;
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 41ae911..6e998c4 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -36,15 +36,14 @@
import android.credentials.CredentialDescription;
import android.credentials.CredentialManager;
import android.credentials.CredentialOption;
+import android.credentials.CredentialProviderInfo;
import android.credentials.GetCredentialException;
import android.credentials.GetCredentialRequest;
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.ICredentialManager;
import android.credentials.IGetCredentialCallback;
-import android.credentials.IListEnabledProvidersCallback;
import android.credentials.ISetEnabledProvidersCallback;
-import android.credentials.ListEnabledProvidersResponse;
import android.credentials.RegisterCredentialDescriptionRequest;
import android.credentials.UnregisterCredentialDescriptionRequest;
import android.credentials.ui.IntentFactory;
@@ -56,7 +55,7 @@
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.service.credentials.CallingAppInfo;
-import android.service.credentials.CredentialProviderInfo;
+import android.service.credentials.CredentialProviderInfoFactory;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@@ -118,10 +117,11 @@
int resolvedUserId) {
List<CredentialManagerServiceImpl> services = new ArrayList<>();
List<CredentialProviderInfo> serviceInfos =
- CredentialProviderInfo.getAvailableSystemServices(
+ CredentialProviderInfoFactory.getAvailableSystemServices(
mContext,
resolvedUserId,
- /* disableSystemAppVerificationForTests= */ false);
+ /* disableSystemAppVerificationForTests= */ false,
+ new HashSet<>());
serviceInfos.forEach(
info -> {
services.add(
@@ -222,10 +222,16 @@
return hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS);
}
- private void verifyPermission(String permission) throws SecurityException {
- if (!hasPermission(permission)) {
- throw new SecurityException("Caller is missing permission: " + permission);
+ private void verifyGetProvidersPermission() throws SecurityException {
+ if (hasPermission(android.Manifest.permission.QUERY_ALL_PACKAGES)) {
+ return;
}
+
+ if (hasPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS)) {
+ return;
+ }
+
+ throw new SecurityException("Caller is missing permission: QUERY_ALL_PACKAGES or LIST_ENABLED_CREDENTIAL_PROVIDERS");
}
private boolean hasPermission(String permission) {
@@ -290,6 +296,12 @@
mContext,
UserHandle.getCallingUserId(),
session,
+ CredentialProviderInfoFactory.getCredentialProviderFromPackageName(
+ mContext, UserHandle.getCallingUserId() ,
+ result.second.mPackageName,
+ CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS,
+ new HashSet<>()),
+ session.mClientAppInfo,
result.second.mPackageName,
result.first));
}
@@ -352,6 +364,14 @@
return providerSessions;
}
+ private List<CredentialProviderInfo> getServicesForCredentialDescription(int userId) {
+ return CredentialProviderInfoFactory.getCredentialProviderServices(
+ mContext,
+ userId,
+ CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS,
+ new HashSet<>());
+ }
+
@Override
@GuardedBy("CredentialDescriptionRegistry.sLock")
public void onUserStopped(@NonNull TargetUser user) {
@@ -550,40 +570,6 @@
providerSessions.forEach(ProviderSession::invokeSession);
}
- @SuppressWarnings("GuardedBy") // ErrorProne requires listEnabledProviders
- // to be guarded by 'service.mLock', which is the same as mLock.
- @Override
- public ICancellationSignal listEnabledProviders(IListEnabledProvidersCallback callback) {
- Log.i(TAG, "listEnabledProviders");
- ICancellationSignal cancelTransport = CancellationSignal.createTransport();
-
- if (!hasWriteSecureSettingsPermission()) {
- try {
- callback.onError(
- PERMISSION_DENIED_ERROR, PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR);
- } catch (RemoteException e) {
- Log.e(TAG, "Issue with invoking response: " + e.getMessage());
- }
- return cancelTransport;
- }
-
- List<String> enabledProviders = new ArrayList<>();
- runForUser(
- (service) -> {
- enabledProviders.add(service.getComponentName().flattenToString());
- });
-
- // Call the callback.
- try {
- callback.onResponse(ListEnabledProvidersResponse.create(enabledProviders));
- } catch (RemoteException e) {
- Log.i(TAG, "Issue with invoking response: " + e.getMessage());
- // TODO: Propagate failure
- }
-
- return cancelTransport;
- }
-
@Override
public void setEnabledProviders(
List<String> providers, int userId, ISetEnabledProvidersCallback callback) {
@@ -659,7 +645,7 @@
// The component name and the package name do not match.
MetricUtilities.logApiCalled(
ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
- ApiStatus.METRICS_API_STATUS_FAILURE, callingUid);
+ ApiStatus.FAILURE, callingUid);
Log.w(
TAG,
"isEnabledCredentialProviderService: Component name does not"
@@ -667,7 +653,7 @@
return false;
}
MetricUtilities.logApiCalled(ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
- ApiStatus.METRICS_API_STATUS_SUCCESS, callingUid);
+ ApiStatus.SUCCESS, callingUid);
return true;
}
}
@@ -677,20 +663,35 @@
}
@Override
- public List<ServiceInfo> getCredentialProviderServices(
- int userId, boolean disableSystemAppVerificationForTests, int providerFilter) {
+ public List<CredentialProviderInfo> getCredentialProviderServices(
+ int userId, int providerFilter) {
Log.i(TAG, "getCredentialProviderServices");
- verifyPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS);
+ verifyGetProvidersPermission();
- List<ServiceInfo> services = new ArrayList<>();
- List<CredentialProviderInfo> providers =
- CredentialProviderInfo.getCredentialProviderServices(
- mContext, userId, disableSystemAppVerificationForTests, providerFilter);
- for (CredentialProviderInfo p : providers) {
- services.add(p.getServiceInfo());
+ return CredentialProviderInfoFactory.getCredentialProviderServices(
+ mContext, userId, providerFilter, getEnabledProviders());
+ }
+
+ @Override
+ public List<CredentialProviderInfo> getCredentialProviderServicesForTesting(
+ int providerFilter) {
+ Log.i(TAG, "getCredentialProviderServicesForTesting");
+ verifyGetProvidersPermission();
+
+ final int userId = UserHandle.getCallingUserId();
+ return CredentialProviderInfoFactory.getCredentialProviderServicesForTesting(
+ mContext, userId, providerFilter, getEnabledProviders());
+ }
+
+ private Set<ServiceInfo> getEnabledProviders() {
+ Set<ServiceInfo> enabledProviders = new HashSet<>();
+ synchronized (mLock) {
+ runForUser(
+ (service) -> {
+ enabledProviders.add(service.getCredentialProviderInfo().getServiceInfo());
+ });
}
-
- return services;
+ return enabledProviders;
}
@Override
@@ -826,14 +827,6 @@
session.executeUnregisterRequest(request, callingPackage);
}
-
- private List<CredentialProviderInfo> getServicesForCredentialDescription(int userId) {
- return CredentialProviderInfo.getCredentialProviderServices(
- mContext,
- userId,
- /* disableSystemAppVerificationForTests= */ false,
- CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS);
- }
}
private void enforceCallingPackage(String callingPackage, int callingUid) {
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index 546c48f..ee55a1c 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -21,7 +21,8 @@
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
-import android.service.credentials.CredentialProviderInfo;
+import android.credentials.CredentialProviderInfo;
+import android.service.credentials.CredentialProviderInfoFactory;
import android.util.Log;
import android.util.Slog;
@@ -82,7 +83,7 @@
Log.i(TAG, "newServiceInfoLocked with null mInfo , "
+ serviceComponent.getPackageName());
}
- mInfo = new CredentialProviderInfo(
+ mInfo = CredentialProviderInfoFactory.create(
getContext(), serviceComponent,
mUserId, /*isSystemProvider=*/false);
return mInfo.getServiceInfo();
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 2c6c0d8..546c37f 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.content.pm.ServiceInfo;
import android.credentials.CredentialManager;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ui.DisabledProviderData;
import android.credentials.ui.IntentFactory;
import android.credentials.ui.ProviderData;
@@ -31,11 +32,12 @@
import android.os.Handler;
import android.os.Looper;
import android.os.ResultReceiver;
-import android.service.credentials.CredentialProviderInfo;
+import android.service.credentials.CredentialProviderInfoFactory;
import android.util.Log;
import android.util.Slog;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -118,11 +120,11 @@
.map(ProviderData::getProviderFlattenedComponentName)
.collect(Collectors.toUnmodifiableSet());
Set<String> allProviders =
- CredentialProviderInfo.getCredentialProviderServices(
+ CredentialProviderInfoFactory.getCredentialProviderServices(
mContext,
mUserId,
- /* disableSystemAppVerificationForTests= */ false,
- CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY)
+ CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY,
+ new HashSet<>())
.stream()
.map(CredentialProviderInfo::getServiceInfo)
.map(ServiceInfo::getComponentName)
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 13f4b54..f59b32c 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -16,12 +16,10 @@
package com.android.server.credentials;
-import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_FAILURE;
-import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_SUCCESS;
-
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.credentials.CredentialProviderInfo;
import android.credentials.GetCredentialException;
import android.credentials.GetCredentialRequest;
import android.credentials.GetCredentialResponse;
@@ -31,11 +29,11 @@
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
-import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import com.android.server.credentials.metrics.ApiName;
import com.android.server.credentials.metrics.ApiStatus;
+import com.android.server.credentials.metrics.ProviderStatusForMetrics;
import java.util.ArrayList;
@@ -43,7 +41,7 @@
* Central session for a single getCredentials request. This class listens to the
* responses from providers, and the UX app, and updates the provider(S) state.
*/
-public final class GetRequestSession extends RequestSession<GetCredentialRequest,
+public class GetRequestSession extends RequestSession<GetCredentialRequest,
IGetCredentialCallback>
implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
private static final String TAG = "GetRequestSession";
@@ -95,11 +93,11 @@
setChosenMetric(componentName);
if (response != null) {
mChosenProviderMetric.setChosenProviderStatus(
- METRICS_PROVIDER_STATUS_FINAL_SUCCESS);
+ ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
respondToClientWithResponseAndFinish(response);
} else {
mChosenProviderMetric.setChosenProviderStatus(
- METRICS_PROVIDER_STATUS_FINAL_FAILURE);
+ ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
"Invalid response from provider");
}
@@ -120,18 +118,18 @@
}
if (isSessionCancelled()) {
logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED);
+ ApiStatus.CLIENT_CANCELED);
finishSession(/*propagateCancellation=*/true);
return;
}
try {
mClientCallback.onResponse(response);
logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_SUCCESS);
+ ApiStatus.SUCCESS);
} catch (RemoteException e) {
Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage());
logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_FAILURE);
+ ApiStatus.FAILURE);
}
finishSession(/*propagateCancellation=*/false);
}
@@ -143,7 +141,7 @@
}
if (isSessionCancelled()) {
logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED);
+ ApiStatus.CLIENT_CANCELED);
finishSession(/*propagateCancellation=*/true);
return;
}
@@ -160,10 +158,10 @@
private void logFailureOrUserCancel(String errorType) {
if (GetCredentialException.TYPE_USER_CANCELED.equals(errorType)) {
logApiCall(ApiName.GET_CREDENTIAL,
- /* apiStatus */ ApiStatus.METRICS_API_STATUS_USER_CANCELED);
+ /* apiStatus */ ApiStatus.USER_CANCELED);
} else {
logApiCall(ApiName.GET_CREDENTIAL,
- /* apiStatus */ ApiStatus.METRICS_API_STATUS_FAILURE);
+ /* apiStatus */ ApiStatus.FAILURE);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index e7b0a2d9..255b2f8 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -16,12 +16,6 @@
package com.android.server.credentials;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN;
-
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -46,18 +40,6 @@
public static final int DEFAULT_INT_32 = -1;
public static final int[] DEFAULT_REPEATED_INT_32 = new int[0];
- // Metrics constants TODO(b/269290341) migrate to enums eventually to improve
- protected static final int METRICS_PROVIDER_STATUS_FINAL_FAILURE =
- CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE;
- protected static final int METRICS_PROVIDER_STATUS_QUERY_FAILURE =
- CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE;
- protected static final int METRICS_PROVIDER_STATUS_FINAL_SUCCESS =
- CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS;
- protected static final int METRICS_PROVIDER_STATUS_QUERY_SUCCESS =
- CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS;
- protected static final int METRICS_PROVIDER_STATUS_UNKNOWN =
- CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN;
-
/**
* This retrieves the uid of any package name, given a context and a component name for the
@@ -108,35 +90,37 @@
protected static void logApiCalled(ApiName apiName, ApiStatus apiStatus,
Map<String, ProviderSession> providers, int callingUid,
ChosenProviderMetric chosenProviderMetric) {
- var providerSessions = providers.values();
- int providerSize = providerSessions.size();
- int[] candidateUidList = new int[providerSize];
- int[] candidateQueryRoundTripTimeList = new int[providerSize];
- int[] candidateStatusList = new int[providerSize];
- int index = 0;
- for (var session : providerSessions) {
- CandidateProviderMetric metric = session.mCandidateProviderMetric;
- candidateUidList[index] = metric.getCandidateUid();
- candidateQueryRoundTripTimeList[index] = metric.getQueryLatencyMicroseconds();
- candidateStatusList[index] = metric.getProviderQueryStatus();
- index++;
+ try {
+ var providerSessions = providers.values();
+ int providerSize = providerSessions.size();
+ int[] candidateUidList = new int[providerSize];
+ int[] candidateQueryRoundTripTimeList = new int[providerSize];
+ int[] candidateStatusList = new int[providerSize];
+ int index = 0;
+ for (var session : providerSessions) {
+ CandidateProviderMetric metric = session.mCandidateProviderMetric;
+ candidateUidList[index] = metric.getCandidateUid();
+ candidateQueryRoundTripTimeList[index] = metric.getQueryLatencyMicroseconds();
+ candidateStatusList[index] = metric.getProviderQueryStatus();
+ index++;
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED,
+ /* api_name */apiName.getMetricCode(),
+ /* caller_uid */ callingUid,
+ /* api_status */ apiStatus.getMetricCode(),
+ /* repeated_candidate_provider_uid */ candidateUidList,
+ /* repeated_candidate_provider_round_trip_time_query_microseconds */
+ candidateQueryRoundTripTimeList,
+ /* repeated_candidate_provider_status */ candidateStatusList,
+ /* chosen_provider_uid */ chosenProviderMetric.getChosenUid(),
+ /* chosen_provider_round_trip_time_overall_microseconds */
+ chosenProviderMetric.getEntireProviderLatencyMicroseconds(),
+ /* chosen_provider_final_phase_microseconds (backwards compat only) */
+ DEFAULT_INT_32,
+ /* chosen_provider_status */ chosenProviderMetric.getChosenProviderStatus());
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
}
- FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED,
- /* api_name */apiName.getMetricCode(),
- /* caller_uid */ callingUid,
- /* api_status */ apiStatus.getMetricCode(),
- /* repeated_candidate_provider_uid */ candidateUidList,
- /* repeated_candidate_provider_round_trip_time_query_microseconds */
- candidateQueryRoundTripTimeList,
- /* repeated_candidate_provider_status */ candidateStatusList,
- /* chosen_provider_uid */ chosenProviderMetric.getChosenUid(),
- /* chosen_provider_round_trip_time_overall_microseconds */
- chosenProviderMetric.getEntireProviderLatencyMicroseconds(),
- /* chosen_provider_final_phase_microseconds (backwards compat only) */
- getMetricTimestampDifferenceMicroseconds(chosenProviderMetric
- .getFinalFinishTimeNanoseconds(),
- chosenProviderMetric.getUiCallEndTimeNanoseconds()),
- /* chosen_provider_status */ chosenProviderMetric.getChosenProviderStatus());
}
/**
@@ -151,20 +135,24 @@
*/
protected static void logApiCalled(ApiName apiName, ApiStatus apiStatus,
int callingUid) {
- FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED,
- /* api_name */apiName.getMetricCode(),
- /* caller_uid */ callingUid,
- /* api_status */ apiStatus.getMetricCode(),
- /* repeated_candidate_provider_uid */ DEFAULT_REPEATED_INT_32,
- /* repeated_candidate_provider_round_trip_time_query_microseconds */
- DEFAULT_REPEATED_INT_32,
- /* repeated_candidate_provider_status */ DEFAULT_REPEATED_INT_32,
- /* chosen_provider_uid */ DEFAULT_INT_32,
- /* chosen_provider_round_trip_time_overall_microseconds */
- DEFAULT_INT_32,
- /* chosen_provider_final_phase_microseconds */
- DEFAULT_INT_32,
- /* chosen_provider_status */ DEFAULT_INT_32);
+ try {
+ FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED,
+ /* api_name */apiName.getMetricCode(),
+ /* caller_uid */ callingUid,
+ /* api_status */ apiStatus.getMetricCode(),
+ /* repeated_candidate_provider_uid */ DEFAULT_REPEATED_INT_32,
+ /* repeated_candidate_provider_round_trip_time_query_microseconds */
+ DEFAULT_REPEATED_INT_32,
+ /* repeated_candidate_provider_status */ DEFAULT_REPEATED_INT_32,
+ /* chosen_provider_uid */ DEFAULT_INT_32,
+ /* chosen_provider_round_trip_time_overall_microseconds */
+ DEFAULT_INT_32,
+ /* chosen_provider_final_phase_microseconds */
+ DEFAULT_INT_32,
+ /* chosen_provider_status */ DEFAULT_INT_32);
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error during metric logging: " + e);
+ }
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 941d9ad..b7a4cd5 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -20,11 +20,11 @@
import android.annotation.UserIdInt;
import android.content.Context;
import android.credentials.ClearCredentialStateException;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ui.ProviderData;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.ClearCredentialStateRequest;
-import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import android.util.Slog;
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 17caba5..640cc33 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -24,6 +24,7 @@
import android.content.Intent;
import android.credentials.CreateCredentialException;
import android.credentials.CreateCredentialResponse;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ui.CreateCredentialProviderData;
import android.credentials.ui.Entry;
import android.credentials.ui.ProviderPendingIntentResponse;
@@ -33,7 +34,6 @@
import android.service.credentials.CallingAppInfo;
import android.service.credentials.CreateCredentialRequest;
import android.service.credentials.CreateEntry;
-import android.service.credentials.CredentialProviderInfo;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.RemoteEntry;
import android.util.Log;
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index b8b11eb..07e2f87 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.Intent;
import android.credentials.CredentialOption;
+import android.credentials.CredentialProviderInfo;
import android.credentials.GetCredentialException;
import android.credentials.GetCredentialResponse;
import android.credentials.ui.AuthenticationEntry;
@@ -35,7 +36,6 @@
import android.service.credentials.BeginGetCredentialResponse;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.CredentialEntry;
-import android.service.credentials.CredentialProviderInfo;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.GetCredentialRequest;
import android.service.credentials.RemoteEntry;
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index 36d6b3d..457806d 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.content.Intent;
import android.credentials.CredentialOption;
+import android.credentials.CredentialProviderInfo;
import android.credentials.GetCredentialException;
import android.credentials.GetCredentialResponse;
import android.credentials.ui.Entry;
@@ -33,6 +34,8 @@
import android.service.credentials.CredentialProviderService;
import android.telecom.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -52,7 +55,8 @@
Set<CredentialDescriptionRegistry.FilterResult>> {
private static final String TAG = "ProviderRegistryGetSession";
- private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
+ @VisibleForTesting
+ static final String CREDENTIAL_ENTRY_KEY = "credential_key";
/** Creates a new provider session to be used by the request session. */
@Nullable
@@ -60,13 +64,16 @@
@NonNull Context context,
@UserIdInt int userId,
@NonNull GetRequestSession getRequestSession,
+ @NonNull CredentialProviderInfo credentialProviderInfo,
+ @NonNull CallingAppInfo callingAppInfo,
@NonNull String credentialProviderPackageName,
@NonNull CredentialOption requestOption) {
return new ProviderRegistryGetSession(
context,
userId,
getRequestSession,
- getRequestSession.mClientAppInfo,
+ credentialProviderInfo,
+ callingAppInfo,
credentialProviderPackageName,
requestOption);
}
@@ -81,15 +88,17 @@
private final String mCredentialProviderPackageName;
@NonNull
private final String mFlattenedRequestOptionString;
- private List<CredentialEntry> mCredentialEntries;
+ @VisibleForTesting
+ List<CredentialEntry> mCredentialEntries;
protected ProviderRegistryGetSession(@NonNull Context context,
@NonNull int userId,
@NonNull GetRequestSession session,
+ @NonNull CredentialProviderInfo credentialProviderInfo,
@NonNull CallingAppInfo callingAppInfo,
@NonNull String servicePackageName,
@NonNull CredentialOption requestOption) {
- super(context, null, requestOption, session, userId, null);
+ super(context, credentialProviderInfo, requestOption, session, userId, null);
mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId);
mCallingAppInfo = callingAppInfo;
mCredentialProviderPackageName = servicePackageName;
@@ -183,7 +192,7 @@
providerPendingIntentResponse.getResultData());
if (getCredentialResponse != null) {
if (mCallbacks != null) {
- mCallbacks.onFinalResponseReceived(mComponentName,
+ ((GetRequestSession) mCallbacks).onFinalResponseReceived(mComponentName,
getCredentialResponse);
}
return;
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 53ed070..a8b9bf6 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -16,9 +16,6 @@
package com.android.server.credentials;
-import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_QUERY_FAILURE;
-import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_QUERY_SUCCESS;
-
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -27,14 +24,15 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.credentials.Credential;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ui.ProviderData;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.os.ICancellationSignal;
import android.os.RemoteException;
-import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import com.android.server.credentials.metrics.CandidateProviderMetric;
+import com.android.server.credentials.metrics.ProviderStatusForMetrics;
import java.util.UUID;
@@ -116,7 +114,7 @@
@Nullable String message);
}
- protected ProviderSession(@NonNull Context context, @NonNull CredentialProviderInfo info,
+ protected ProviderSession(@NonNull Context context, @Nullable CredentialProviderInfo info,
@NonNull T providerRequest,
@Nullable ProviderInternalCallback callbacks,
@NonNull int userId,
@@ -205,9 +203,11 @@
mCandidateProviderMetric
.setQueryFinishTimeNanoseconds(System.nanoTime());
if (isTerminatingStatus(status)) {
- mCandidateProviderMetric.setProviderQueryStatus(METRICS_PROVIDER_STATUS_QUERY_FAILURE);
+ mCandidateProviderMetric.setProviderQueryStatus(ProviderStatusForMetrics.QUERY_FAILURE
+ .getMetricCode());
} else if (isCompletionStatus(status)) {
- mCandidateProviderMetric.setProviderQueryStatus(METRICS_PROVIDER_STATUS_QUERY_SUCCESS);
+ mCandidateProviderMetric.setProviderQueryStatus(ProviderStatusForMetrics.QUERY_SUCCESS
+ .getMetricCode());
}
}
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index abfb210..c1f35d0 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -22,6 +22,7 @@
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ui.ProviderData;
import android.credentials.ui.UserSelectionDialogResult;
import android.os.Binder;
@@ -30,7 +31,6 @@
import android.os.IBinder;
import android.os.Looper;
import android.service.credentials.CallingAppInfo;
-import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import com.android.internal.R;
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
index 36a1f2d..22cab70 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
@@ -22,11 +22,11 @@
import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_USER_CANCELED;
public enum ApiStatus {
- METRICS_API_STATUS_SUCCESS(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_SUCCESS),
- METRICS_API_STATUS_FAILURE(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_FAILURE),
- METRICS_API_STATUS_CLIENT_CANCELED(
+ SUCCESS(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_SUCCESS),
+ FAILURE(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_FAILURE),
+ CLIENT_CANCELED(
CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_CLIENT_CANCELED),
- METRICS_API_STATUS_USER_CANCELED(
+ USER_CANCELED(
CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_USER_CANCELED);
private final int mInnerMetricCode;
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
new file mode 100644
index 0000000..37ec8f0
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 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.credentials.metrics;
+
+/**
+ * A part of the Candidate Phase, but emitted alongside {@link ChosenProviderMetric}. The user is
+ * shown various entries from the provider responses, and may selectively browse through many
+ * entries. It is possible that the initial set of browsing is for a provider that is ultimately
+ * not chosen. This metric will be gathered PER browsing click, and aggregated, so that we can
+ * understand where user interaction is more cumbersome, informing us for future improvements. This
+ * can only be complete when the browsing is finished, ending in a final user choice, or possibly
+ * a cancellation. Thus, this will be collected and emitted in the final phase, though collection
+ * will begin in the candidate phase when the user begins browsing options.
+ */
+public class CandidateBrowsingPhaseMetric {
+
+ private static final String TAG = "CandidateSelectionPhaseMetric";
+ private static final int SEQUENCE_ID = 3;
+ // The session id associated with the API Call this candidate provider is a part of, default -1
+ private int mSessionId = -1;
+ // The EntryEnum that was pressed, defaults to -1 (TODO immediately, generate entry enum).
+ private int mEntryEnum = -1;
+ // The provider associated with the press, defaults to -1
+ private int mProviderUid = -1;
+
+ /* -- The session ID -- */
+
+ public void setSessionId(int sessionId) {
+ mSessionId = sessionId;
+ }
+
+ public int getSessionId() {
+ return mSessionId;
+ }
+
+ /* -- The sequence ID -- */
+
+ public int getSequenceId() {
+ return SEQUENCE_ID;
+ }
+
+ /* -- The Entry of this tap -- */
+
+ public void setEntryEnum(int entryEnum) {
+ mEntryEnum = entryEnum;
+ }
+
+ public int getEntryEnum() {
+ return mEntryEnum;
+ }
+
+ /* -- The Provider UID of this Tap -- */
+
+ public void setProviderUid(int providerUid) {
+ mProviderUid = providerUid;
+ }
+
+ public int getProviderUid() {
+ return mProviderUid;
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java
index f49995d0..9f438ec 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java
@@ -18,6 +18,9 @@
/**
* The central candidate provider metric object that mimics our defined metric setup.
+ * Some types are redundant across these metric collectors, but that has debug use-cases as
+ * these data-types are available at different moments of the flow (and typically, one can feed
+ * into the next).
* TODO(b/270403549) - iterate on this in V3+
*/
public class CandidateProviderMetric {
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java
index 75fdc56..0310255 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java
@@ -22,6 +22,9 @@
/**
* The central chosen provider metric object that mimics our defined metric setup.
+ * Some types are redundant across these metric collectors, but that has debug use-cases as
+ * these data-types are available at different moments of the flow (and typically, one can feed
+ * into the next).
* TODO(b/270403549) - iterate on this in V3+
*/
public class ChosenProviderMetric {
@@ -65,12 +68,12 @@
/**
* In order for a chosen provider to be selected, the call must have successfully begun.
- * Thus, the {@link PreCandidateMetric} can directly pass this initial latency figure into
+ * Thus, the {@link InitialPhaseMetric} can directly pass this initial latency figure into
* this chosen provider metric.
*
* @param preQueryPhaseLatencyMicroseconds the millisecond latency for the service start,
* typically passed in through the
- * {@link PreCandidateMetric}
+ * {@link InitialPhaseMetric}
*/
public void setPreQueryPhaseLatencyMicroseconds(int preQueryPhaseLatencyMicroseconds) {
mPreQueryPhaseLatencyMicroseconds = preQueryPhaseLatencyMicroseconds;
@@ -112,7 +115,7 @@
/**
* Returns the full (platform invoked to response) latency in microseconds. Expects the
- * start time to be provided, such as from {@link PreCandidateMetric}.
+ * start time to be provided, such as from {@link InitialPhaseMetric}.
*/
public int getEntireLatencyMicroseconds() {
return (int) ((this.mFinalFinishTimeNanoseconds
@@ -123,11 +126,11 @@
/**
* In order for a chosen provider to be selected, the call must have successfully begun.
- * Thus, the {@link PreCandidateMetric} can directly pass this initial timestamp into this
+ * Thus, the {@link InitialPhaseMetric} can directly pass this initial timestamp into this
* chosen provider metric.
*
* @param serviceBeganTimeNanoseconds the timestamp moment when the platform was called,
- * typically passed in through the {@link PreCandidateMetric}
+ * typically passed in through the {@link InitialPhaseMetric}
*/
public void setServiceBeganTimeNanoseconds(long serviceBeganTimeNanoseconds) {
mServiceBeganTimeNanoseconds = serviceBeganTimeNanoseconds;
@@ -188,8 +191,6 @@
- this.mServiceBeganTimeNanoseconds) / 1000);
}
-
-
/* ----------- Provider Status -------------- */
public int getChosenProviderStatus() {
diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
new file mode 100644
index 0000000..5f062b0
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 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.credentials.metrics;
+
+/**
+ * This handles metrics collected prior to any remote calls to providers.
+ * Some types are redundant across these metric collectors, but that has debug use-cases as
+ * these data-types are available at different moments of the flow (and typically, one can feed
+ * into the next).
+ * TODO(b/270403549) - iterate on this in V3+
+ */
+public class InitialPhaseMetric {
+ private static final String TAG = "PreCandidateMetric";
+
+ // The api being called, default set to unknown
+ private int mApiName = ApiName.UNKNOWN.getMetricCode();
+ // The caller uid of the calling application, default to -1
+ private int mCallerUid = -1;
+ // The session id to unite multiple atom emits, default to -1
+ private long mSessionId = -1;
+ // A sequence id to order united emits, default to -1
+ private int mSequenceId = -1;
+ private int mCountRequestClassType = -1;
+
+ // Raw timestamps in nanoseconds, *the only* one logged as such (i.e. 64 bits) since it is a
+ // reference point.
+ private long mCredentialServiceStartedTimeNanoseconds = -1;
+
+ // A reference point to give this object utility to capture latency. Can be directly handed
+ // over to the next latency object.
+ private long mCredentialServiceBeginQueryTimeNanoseconds = -1;
+
+
+ public InitialPhaseMetric() {
+ }
+
+ /* ---------- Latencies ---------- */
+
+ /* -- Direct Latencies -- */
+
+ public int getServiceStartToQueryLatencyMicroseconds() {
+ return (int) ((this.mCredentialServiceStartedTimeNanoseconds
+ - this.mCredentialServiceBeginQueryTimeNanoseconds) / 1000);
+ }
+
+ /* -- Timestamps -- */
+
+ public void setCredentialServiceStartedTimeNanoseconds(
+ long credentialServiceStartedTimeNanoseconds
+ ) {
+ this.mCredentialServiceStartedTimeNanoseconds = credentialServiceStartedTimeNanoseconds;
+ }
+
+ public void setCredentialServiceBeginQueryTimeNanoseconds(
+ long credentialServiceBeginQueryTimeNanoseconds) {
+ mCredentialServiceBeginQueryTimeNanoseconds = credentialServiceBeginQueryTimeNanoseconds;
+ }
+
+ public long getCredentialServiceStartedTimeNanoseconds() {
+ return mCredentialServiceStartedTimeNanoseconds;
+ }
+
+ public long getCredentialServiceBeginQueryTimeNanoseconds() {
+ return mCredentialServiceBeginQueryTimeNanoseconds;
+ }
+
+ /* ------ ApiName ------ */
+
+ public void setApiName(int apiName) {
+ mApiName = apiName;
+ }
+
+ public int getApiName() {
+ return mApiName;
+ }
+
+ /* ------ CallerUid ------ */
+
+ public void setCallerUid(int callerUid) {
+ mCallerUid = callerUid;
+ }
+
+ public int getCallerUid() {
+ return mCallerUid;
+ }
+
+ /* ------ SessionId ------ */
+
+ public void setSessionId(long sessionId) {
+ mSessionId = sessionId;
+ }
+
+ public long getSessionId() {
+ return mSessionId;
+ }
+
+ /* ------ SequenceId ------ */
+
+ public void setSequenceId(int sequenceId) {
+ mSequenceId = sequenceId;
+ }
+
+ public int getSequenceId() {
+ return mSequenceId;
+ }
+
+ /* ------ Count Request Class Types ------ */
+
+ public void setCountRequestClassType(int countRequestClassType) {
+ mCountRequestClassType = countRequestClassType;
+ }
+
+ public int getCountRequestClassType() {
+ return mCountRequestClassType;
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/PreCandidateMetric.java b/services/credentials/java/com/android/server/credentials/metrics/PreCandidateMetric.java
deleted file mode 100644
index 952328f..0000000
--- a/services/credentials/java/com/android/server/credentials/metrics/PreCandidateMetric.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2023 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.credentials.metrics;
-
-/**
- * This handles metrics collected prior to any remote calls to providers.
- * TODO(b/270403549) - iterate on this in V3+
- */
-public class PreCandidateMetric {
-
- private static final String TAG = "PreCandidateMetric";
-
- // Raw timestamps in nanoseconds, *the only* one logged as such (i.e. 64 bits) since it is a
- // reference point.
-
- private long mCredentialServiceStartedTimeNanoseconds = -1;
- private long mCredentialServiceBeginQueryTimeNanoseconds = -1;
-
- public PreCandidateMetric() {
- }
-
- /* ---------- Latencies ---------- */
-
- /* -- Direct Latencies -- */
-
- public int getServiceStartToQueryLatencyMicroseconds() {
- return (int) ((this.mCredentialServiceStartedTimeNanoseconds
- - this.mCredentialServiceBeginQueryTimeNanoseconds) / 1000);
- }
-
- /* -- Timestamps -- */
-
- public void setCredentialServiceStartedTimeNanoseconds(
- long credentialServiceStartedTimeNanoseconds
- ) {
- this.mCredentialServiceStartedTimeNanoseconds = credentialServiceStartedTimeNanoseconds;
- }
-
- public void setCredentialServiceBeginQueryTimeNanoseconds(
- long credentialServiceBeginQueryTimeNanoseconds) {
- mCredentialServiceBeginQueryTimeNanoseconds = credentialServiceBeginQueryTimeNanoseconds;
- }
-
- public long getCredentialServiceStartedTimeNanoseconds() {
- return mCredentialServiceStartedTimeNanoseconds;
- }
-
- public long getCredentialServiceBeginQueryTimeNanoseconds() {
- return mCredentialServiceBeginQueryTimeNanoseconds;
- }
-}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java
new file mode 100644
index 0000000..08f1afa
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 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.credentials.metrics;
+
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN;
+
+public enum ProviderStatusForMetrics {
+
+ UNKNOWN(
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN),
+ FINAL_FAILURE(
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE),
+ QUERY_FAILURE(
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE),
+ FINAL_SUCCESS(
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS),
+ QUERY_SUCCESS(
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS);
+
+ private final int mInnerMetricCode;
+
+ ProviderStatusForMetrics(int innerMetricCode) {
+ this.mInnerMetricCode = innerMetricCode;
+ }
+
+ /**
+ * Gives the West-world version of the metric name.
+ *
+ * @return a code corresponding to the west world metric name
+ */
+ public int getMetricCode() {
+ return this.mInnerMetricCode;
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
index 4351bc1..80100a9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
@@ -24,6 +24,8 @@
import com.android.internal.annotations.GuardedBy;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -52,6 +54,11 @@
@GuardedBy("mLock")
private final SparseIntArray mPermissionPolicy = new SparseIntArray();
+ @GuardedBy("mLock")
+ private List<String> mLauncherShortcutOverrides =
+ new ArrayList<>();
+
+
/** Maps to {@code ActiveAdmin.mAdminCanGrantSensorsPermissions}. */
private final AtomicBoolean mCanGrantSensorsPermissions = new AtomicBoolean(false);
@@ -122,6 +129,22 @@
mCanGrantSensorsPermissions.set(canGrant);
}
+ @Override
+ public List<String> getLauncherShortcutOverrides() {
+ synchronized (mLock) {
+ return new ArrayList<>(mLauncherShortcutOverrides);
+ }
+ }
+
+ /**
+ * Sets a list of packages for which shortcuts should be replaced by their badged version.
+ */
+ public void setLauncherShortcutOverrides(List<String> launcherShortcutOverrides) {
+ synchronized (mLock) {
+ mLauncherShortcutOverrides = new ArrayList<>(launcherShortcutOverrides);
+ }
+ }
+
/** Dump content */
public void dump(IndentingPrintWriter pw) {
synchronized (mLock) {
@@ -131,6 +154,8 @@
pw.println("Password quality: " + mPasswordQuality);
pw.println("Permission policy: " + mPermissionPolicy);
pw.println("Admin can grant sensors permission: " + mCanGrantSensorsPermissions.get());
+ pw.print("Shortcuts overrides: ");
+ pw.println(mLauncherShortcutOverrides);
pw.decreaseIndent();
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 400ee1d..a4e563b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3522,16 +3522,30 @@
userId == UserHandle.USER_SYSTEM ? UserHandle.USER_ALL : userId);
updatePermissionPolicyCache(userId);
updateAdminCanGrantSensorsPermissionCache(userId);
-
final List<PreferentialNetworkServiceConfig> preferentialNetworkServiceConfigs;
+ boolean isManagedSubscription;
+
synchronized (getLockObject()) {
ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
preferentialNetworkServiceConfigs = owner != null
? owner.mPreferentialNetworkServiceConfigs
: List.of(PreferentialNetworkServiceConfig.DEFAULT);
+
+ isManagedSubscription = owner != null && owner.mManagedSubscriptionsPolicy != null
+ && owner.mManagedSubscriptionsPolicy.getPolicyType()
+ == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS;
}
updateNetworkPreferenceForUser(userId, preferentialNetworkServiceConfigs);
+ if (isManagedSubscription) {
+ String defaultDialerPackageName = getDefaultRoleHolderPackageName(
+ com.android.internal.R.string.config_defaultDialer);
+ String defaultSmsPackageName = getDefaultRoleHolderPackageName(
+ com.android.internal.R.string.config_defaultSms);
+ updateDialerAndSmsManagedShortcutsOverrideCache(defaultDialerPackageName,
+ defaultSmsPackageName);
+ }
+
startOwnerService(userId, "start-user");
if (isDevicePolicyEngineEnabled()) {
mDevicePolicyEngine.handleStartUser(userId);
@@ -7614,6 +7628,7 @@
if (isWorkProfileTelephonyFlagEnabled()) {
clearManagedSubscriptionsPolicy();
+ clearLauncherShortcutOverrides();
updateTelephonyCrossProfileIntentFilters(parentId, UserHandle.USER_NULL, false);
}
Slogf.i(LOG_TAG, "Cleaning up device-wide policies done.");
@@ -7631,6 +7646,10 @@
}
}
+ private void clearLauncherShortcutOverrides() {
+ mPolicyCache.setLauncherShortcutOverrides(new ArrayList<>());
+ }
+
private void updateTelephonyCrossProfileIntentFilters(int parentUserId, int profileUserId,
boolean enableWorkTelephony) {
try {
@@ -22755,12 +22774,30 @@
} else {
Slogf.w(LOG_TAG, "Couldn't install sms app, sms app package is null");
}
+
+ updateDialerAndSmsManagedShortcutsOverrideCache(defaultDialerPackageName,
+ defaultSmsPackageName);
} catch (RemoteException re) {
// shouldn't happen
Slogf.wtf(LOG_TAG, "Failed to install dialer/sms app", re);
}
}
+ private void updateDialerAndSmsManagedShortcutsOverrideCache(
+ String defaultDialerPackageName, String defaultSmsPackageName) {
+
+ List<String> shortcutOverrides = new ArrayList<>();
+
+ if (defaultDialerPackageName != null) {
+ shortcutOverrides.add(defaultDialerPackageName);
+ }
+
+ if (defaultSmsPackageName != null) {
+ shortcutOverrides.add(defaultSmsPackageName);
+ }
+ mPolicyCache.setLauncherShortcutOverrides(shortcutOverrides);
+ }
+
private void registerListenerToAssignSubscriptionsToUser(int userId) {
synchronized (mSubscriptionsChangedListenerLock) {
if (mSubscriptionsChangedListener != null) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 3ca158d..194647fd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -120,7 +120,9 @@
} else {
mDeviceStateCache.setDeviceOwnerType(NO_DEVICE_OWNER);
}
-
+ for (int userId : usersIds) {
+ mDeviceStateCache.setHasProfileOwner(userId, hasProfileOwner(userId));
+ }
} else {
mUserManagerInternal.setDeviceManaged(hasDeviceOwner());
for (int userId : usersIds) {
diff --git a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
index 0993295..2505abf2 100644
--- a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
@@ -152,6 +152,8 @@
}
} catch (SQLiteException exception) {
Slog.w("SQLite exception when querying contacts.", exception);
+ } catch (IllegalArgumentException exception) {
+ Slog.w("Illegal Argument exception when querying contacts.", exception);
}
if (found && lookupKey != null && hasPhoneNumber) {
return queryPhoneNumber(lookupKey);
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 0ca4dfc..54d2c19 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -129,7 +129,6 @@
private final List<PeopleService.ConversationsListener> mConversationsListeners =
new ArrayList<>(1);
private final Handler mHandler;
-
private ContentObserver mCallLogContentObserver;
private ContentObserver mMmsSmsContentObserver;
@@ -1106,6 +1105,7 @@
@NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
mInjector.getBackgroundExecutor().execute(() -> {
PackageData packageData = getPackage(packageName, user.getIdentifier());
+ boolean hasCachedShortcut = false;
for (ShortcutInfo shortcut : shortcuts) {
if (ShortcutHelper.isConversationShortcut(
shortcut, mShortcutServiceInternal, user.getIdentifier())) {
@@ -1114,15 +1114,18 @@
? packageData.getConversationInfo(shortcut.getId()) : null;
if (conversationInfo == null
|| !conversationInfo.isShortcutCachedForNotification()) {
- // This is a newly cached shortcut. Clean up the existing cached
- // shortcuts to ensure the cache size is under the limit.
- cleanupCachedShortcuts(user.getIdentifier(),
- MAX_CACHED_RECENT_SHORTCUTS - 1);
+ hasCachedShortcut = true;
}
}
addOrUpdateConversationInfo(shortcut);
}
}
+ // Added at least one new conversation. Uncache older existing cached
+ // shortcuts to ensure the cache size is under the limit.
+ if (hasCachedShortcut) {
+ cleanupCachedShortcuts(user.getIdentifier(),
+ MAX_CACHED_RECENT_SHORTCUTS);
+ }
});
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index cd5ac7bc..1731590 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -782,6 +782,34 @@
assertTrue(mIntf.isUidInForeground(UID));
}
+ @Test
+ public void testAppWidgetVisibleDoesntChangeUidState() {
+ procStateBuilder(UID)
+ .topState()
+ .update();
+
+ SparseArray<String> updatedAppWidgetVisibilities = new SparseArray<>();
+ updatedAppWidgetVisibilities.put(UID, "");
+
+ mIntf.updateAppWidgetVisibility(updatedAppWidgetVisibilities, true);
+
+ assertEquals(UID_STATE_TOP, mIntf.getUidState(UID));
+ }
+
+ @Test
+ public void testAppWidgetNotVisibleDoesntChangeUidState() {
+ SparseArray<String> updatedAppWidgetVisibilities = new SparseArray<>();
+ updatedAppWidgetVisibilities.put(UID, "");
+ mIntf.updateAppWidgetVisibility(updatedAppWidgetVisibilities, true);
+ procStateBuilder(UID)
+ .topState()
+ .update();
+
+ mIntf.updateAppWidgetVisibility(updatedAppWidgetVisibilities, false);
+
+ assertEquals(UID_STATE_TOP, mIntf.getUidState(UID));
+ }
+
public void testUidStateChangedCallback(int initialState, int finalState) {
int initialUidState = processStateToUidState(initialState);
int finalUidState = processStateToUidState(finalState);
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 7942e24..19aae19 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -643,6 +643,34 @@
verify(mHolder.screenOffBrightnessSensorController).stop();
}
+ @Test
+ public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
+ float brightness = 0.3f;
+ float nits = 500;
+ when(mResourcesMock.getBoolean(
+ com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay))
+ .thenReturn(true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+
+ mHolder.dpc.setBrightness(brightness);
+ verify(mHolder.brightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
+
+ float newBrightness = 0.4f;
+ when(mHolder.brightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
+ when(mHolder.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(newBrightness);
+ // New display device
+ setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+ mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+ // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
+ verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
+ }
+
private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
String uniqueId) {
return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index 16bf2a22..02b6ea0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -488,17 +488,17 @@
// We should still set screen state for the default display
DisplayPowerRequest dpr = new DisplayPowerRequest();
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1);
+ advanceTime(1); // Run updatePowerState
verify(mHolder.displayPowerState, times(2)).setScreenState(anyInt());
mHolder = createDisplayPowerController(42, UNIQUE_ID);
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1);
+ advanceTime(1); // Run updatePowerState
verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
mHolder.dpc.onBootCompleted();
- advanceTime(1);
+ advanceTime(1); // Run updatePowerState
verify(mHolder.displayPowerState).setScreenState(anyInt());
}
@@ -647,6 +647,34 @@
verify(mHolder.screenOffBrightnessSensorController).stop();
}
+ @Test
+ public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
+ float brightness = 0.3f;
+ float nits = 500;
+ when(mResourcesMock.getBoolean(
+ com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay))
+ .thenReturn(true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+
+ mHolder.dpc.setBrightness(brightness);
+ verify(mHolder.brightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
+
+ float newBrightness = 0.4f;
+ when(mHolder.brightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
+ when(mHolder.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(newBrightness);
+ // New display device
+ setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+ mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+ // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
+ verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
+ }
+
private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
String uniqueId) {
return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
index f334a6a..3ba5d1e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
@@ -41,7 +41,7 @@
import androidx.test.runner.AndroidJUnit4;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
@@ -180,7 +180,7 @@
}
private void waitForNonDelayedMessagesProcessed() {
- JobSchedulerBackgroundThread.getHandler().runWithScissors(() -> {}, 15_000);
+ AppSchedulingModuleThread.getHandler().runWithScissors(() -> {}, 15_000);
}
private JobInfo.Builder createBaseJobInfoBuilder(int jobId) {
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 7149265..6861c2f 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -108,6 +108,7 @@
<uses-permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB" />
<uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
+ <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
<queries>
<package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
new file mode 100644
index 0000000..3d52ac5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2023 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.credentials;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.app.Activity;
+import android.app.slice.Slice;
+import android.app.slice.SliceSpec;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ServiceInfo;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.SigningInfo;
+import android.credentials.Credential;
+import android.credentials.CredentialOption;
+import android.credentials.CredentialProviderInfo;
+import android.credentials.GetCredentialException;
+import android.credentials.GetCredentialResponse;
+import android.credentials.ui.GetCredentialProviderData;
+import android.credentials.ui.ProviderPendingIntentResponse;
+import android.net.Uri;
+import android.os.Bundle;
+import android.service.credentials.CallingAppInfo;
+import android.service.credentials.CredentialEntry;
+import android.service.credentials.CredentialProviderInfoFactory;
+import android.service.credentials.CredentialProviderService;
+import android.service.credentials.GetCredentialRequest;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.security.cert.CertificateException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests for CredentialDescriptionRegistry.
+ *
+ * atest FrameworksServicesTests:com.android.server.credentials.ProviderRegistryGetSessionTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ProviderRegistryGetSessionTest {
+
+ private static final String CALLING_PACKAGE_NAME = "com.credman.app";
+ private static final int USER_ID_1 = 1;
+ private static final String FLATTENED_REQUEST = "FLATTENED_REQ";
+ private static final String CP_SERVICE_NAME = "CredentialProvider";
+ private static final ComponentName CREDENTIAL_PROVIDER_COMPONENT =
+ new ComponentName(CALLING_PACKAGE_NAME, CP_SERVICE_NAME);
+ private static final String GET_CREDENTIAL_EXCEPTION_TYPE = "TYPE";
+ private static final String CREDENTIAL_TYPE = "MDOC";
+ private static final String GET_CREDENTIAL_EXCEPTION_MESSAGE = "MESSAGE";
+
+ private ProviderRegistryGetSession mProviderRegistryGetSession;
+ @Mock private GetRequestSession mGetRequestSession;
+ private CredentialOption mGetCredentialOption;
+ @Mock private ServiceInfo mServiceInfo;
+ private CredentialProviderInfo mCredentialProviderInfo;
+ private CallingAppInfo mCallingAppInfo;
+ @Mock private CredentialDescriptionRegistry mCredentialDescriptionRegistry;
+ private Bundle mRetrievalData;
+ @Mock private CredentialEntry mEntry;
+ @Mock private CredentialEntry mEntry2;
+ private Slice mSlice;
+ private Slice mSlice2;
+ private CredentialDescriptionRegistry.FilterResult mResult;
+ private Set<CredentialDescriptionRegistry.FilterResult> mResponse;
+
+ @SuppressWarnings("GuardedBy")
+ @Before
+ public void setUp() throws CertificateException {
+ MockitoAnnotations.initMocks(this);
+ final Context context = ApplicationProvider.getApplicationContext();
+ mRetrievalData = new Bundle();
+ mRetrievalData.putString(CredentialOption.FLATTENED_REQUEST, FLATTENED_REQUEST);
+ mCallingAppInfo = createCallingAppInfo();
+ mGetCredentialOption = new CredentialOption(CREDENTIAL_TYPE, mRetrievalData,
+ new Bundle(), false);
+ when(mServiceInfo.getComponentName()).thenReturn(CREDENTIAL_PROVIDER_COMPONENT);
+ mCredentialProviderInfo = CredentialProviderInfoFactory
+ .createForTests(mServiceInfo,
+ /* overrideLabel= */ "test",
+ /* isSystemProvider= */ false,
+ /* isEnabled= */ true,
+ /* capabilities= */ Collections.EMPTY_LIST);
+ CredentialDescriptionRegistry.setSession(USER_ID_1, mCredentialDescriptionRegistry);
+ mResponse = new HashSet<>();
+ mSlice = createSlice();
+ mSlice2 = createSlice();
+ when(mEntry.getSlice()).thenReturn(mSlice);
+ when(mEntry2.getSlice()).thenReturn(mSlice2);
+ mResult = new CredentialDescriptionRegistry.FilterResult(CALLING_PACKAGE_NAME,
+ FLATTENED_REQUEST,
+ List.of(mEntry, mEntry2));
+ mResponse.add(mResult);
+ when(mCredentialDescriptionRegistry.getFilteredResultForProvider(anyString(), anyString()))
+ .thenReturn(mResponse);
+ mProviderRegistryGetSession = ProviderRegistryGetSession
+ .createNewSession(context, USER_ID_1, mGetRequestSession, mCredentialProviderInfo,
+ mCallingAppInfo,
+ CALLING_PACKAGE_NAME,
+ mGetCredentialOption);
+ }
+
+ @Test
+ public void testInvokeSession_existingProvider_setsResults() {
+ final ArgumentCaptor<String> packageNameCaptor = ArgumentCaptor.forClass(String.class);
+ final ArgumentCaptor<String> flattenedRequestCaptor = ArgumentCaptor.forClass(String.class);
+ final ArgumentCaptor<ProviderSession.Status> statusCaptor =
+ ArgumentCaptor.forClass(ProviderSession.Status.class);
+ final ArgumentCaptor<ComponentName> cpComponentNameCaptor =
+ ArgumentCaptor.forClass(ComponentName.class);
+
+ mProviderRegistryGetSession.invokeSession();
+
+ verify(mCredentialDescriptionRegistry).getFilteredResultForProvider(
+ packageNameCaptor.capture(),
+ flattenedRequestCaptor.capture());
+ assertThat(packageNameCaptor.getValue()).isEqualTo(CALLING_PACKAGE_NAME);
+ assertThat(flattenedRequestCaptor.getValue()).isEqualTo(FLATTENED_REQUEST);
+ verify(mGetRequestSession).onProviderStatusChanged(statusCaptor.capture(),
+ cpComponentNameCaptor.capture());
+ assertThat(statusCaptor.getValue()).isEqualTo(ProviderSession.Status.CREDENTIALS_RECEIVED);
+ assertThat(cpComponentNameCaptor.getValue()).isEqualTo(CREDENTIAL_PROVIDER_COMPONENT);
+ assertThat(mProviderRegistryGetSession.mCredentialEntries).hasSize(2);
+ assertThat(mProviderRegistryGetSession.mCredentialEntries.get(0)).isSameInstanceAs(mEntry);
+ assertThat(mProviderRegistryGetSession.mCredentialEntries.get(1)).isSameInstanceAs(mEntry2);
+ }
+
+ @Test
+ public void testPrepareUiData_statusNonUIInvoking_throwsIllegalStateException() {
+ mProviderRegistryGetSession.setStatus(ProviderSession.Status.CREDENTIALS_RECEIVED);
+
+ assertThrows(IllegalStateException.class,
+ () -> mProviderRegistryGetSession.prepareUiData());
+ }
+
+ @Test
+ public void testPrepareUiData_statusUIInvokingNoResults_returnsNull() {
+ mProviderRegistryGetSession.setStatus(ProviderSession.Status.CANCELED);
+
+ assertThat(mProviderRegistryGetSession.prepareUiData()).isNull();
+ }
+
+ @Test
+ public void testPrepareUiData_invokeCalledSuccessfully_returnsCorrectData() {
+ mProviderRegistryGetSession.invokeSession();
+ GetCredentialProviderData providerData = (GetCredentialProviderData)
+ mProviderRegistryGetSession.prepareUiData();
+
+ assertThat(providerData).isNotNull();
+ assertThat(providerData.getCredentialEntries()).hasSize(2);
+ assertThat(providerData.getCredentialEntries().get(0).getSlice()).isSameInstanceAs(mSlice);
+ assertThat(providerData.getCredentialEntries().get(1).getSlice()).isSameInstanceAs(mSlice2);
+ Intent intent = providerData.getCredentialEntries().get(0).getFrameworkExtrasIntent();
+ GetCredentialRequest getRequest = intent.getParcelableExtra(CredentialProviderService
+ .EXTRA_GET_CREDENTIAL_REQUEST, GetCredentialRequest.class);
+ assertThat(getRequest.getCallingAppInfo()).isSameInstanceAs(mCallingAppInfo);
+ assertThat(getRequest.getCredentialOptions().get(0)).isSameInstanceAs(mGetCredentialOption);
+ Intent intent2 = providerData.getCredentialEntries().get(0).getFrameworkExtrasIntent();
+ GetCredentialRequest getRequest2 = intent2.getParcelableExtra(CredentialProviderService
+ .EXTRA_GET_CREDENTIAL_REQUEST, GetCredentialRequest.class);
+ assertThat(getRequest2.getCallingAppInfo()).isSameInstanceAs(mCallingAppInfo);
+ assertThat(getRequest2.getCredentialOptions().get(0))
+ .isSameInstanceAs(mGetCredentialOption);
+ }
+
+ @Test
+ public void testOnUiEntrySelected_wrongEntryKey_doesNothing() {
+ final Intent intent = new Intent();
+ final GetCredentialResponse response =
+ new GetCredentialResponse(new Credential(CREDENTIAL_TYPE, new Bundle()));
+ intent.putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, response);
+ final ProviderPendingIntentResponse providerPendingIntentResponse = new
+ ProviderPendingIntentResponse(Activity.RESULT_OK, intent);
+
+ mProviderRegistryGetSession.onUiEntrySelected(
+ ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY,
+ "unsupportedKey", providerPendingIntentResponse);
+
+ verifyZeroInteractions(mGetRequestSession);
+ }
+
+ @Test
+ public void testOnUiEntrySelected_nullPendingIntentResponse_doesNothing() {
+ mProviderRegistryGetSession.onUiEntrySelected(
+ ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY,
+ ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY, null);
+
+ verifyZeroInteractions(mGetRequestSession);
+ }
+
+ @Test
+ public void testOnUiEntrySelected_pendingIntentWithException_callbackWithGivenException() {
+ final ArgumentCaptor<String> exceptionTypeCaptor =
+ ArgumentCaptor.forClass(String.class);
+ final ArgumentCaptor<String> exceptionMessageCaptor =
+ ArgumentCaptor.forClass(String.class);
+ final ArgumentCaptor<ComponentName> cpComponentNameCaptor =
+ ArgumentCaptor.forClass(ComponentName.class);
+ final ArgumentCaptor<ProviderSession.Status> statusCaptor =
+ ArgumentCaptor.forClass(ProviderSession.Status.class);
+ final GetCredentialException exception =
+ new GetCredentialException(GET_CREDENTIAL_EXCEPTION_TYPE,
+ GET_CREDENTIAL_EXCEPTION_MESSAGE);
+ mProviderRegistryGetSession.invokeSession();
+ GetCredentialProviderData providerData = (GetCredentialProviderData)
+ mProviderRegistryGetSession.prepareUiData();
+ String entryKey = providerData.getCredentialEntries().get(0).getSubkey();
+ final Intent intent = new Intent();
+ intent.putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION, exception);
+ final ProviderPendingIntentResponse providerPendingIntentResponse = new
+ ProviderPendingIntentResponse(Activity.RESULT_OK, intent);
+
+ mProviderRegistryGetSession.onUiEntrySelected(
+ ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY,
+ entryKey, providerPendingIntentResponse);
+
+ verify(mGetRequestSession).onProviderStatusChanged(statusCaptor.capture(),
+ cpComponentNameCaptor.capture());
+ assertThat(statusCaptor.getValue()).isEqualTo(ProviderSession.Status.CREDENTIALS_RECEIVED);
+ verify(mGetRequestSession).onFinalErrorReceived(cpComponentNameCaptor.capture(),
+ exceptionTypeCaptor.capture(), exceptionMessageCaptor.capture());
+ assertThat(cpComponentNameCaptor.getValue())
+ .isSameInstanceAs(CREDENTIAL_PROVIDER_COMPONENT);
+ assertThat(exceptionTypeCaptor.getValue()).isEqualTo(GET_CREDENTIAL_EXCEPTION_TYPE);
+ assertThat(exceptionMessageCaptor.getValue()).isEqualTo(GET_CREDENTIAL_EXCEPTION_MESSAGE);
+ }
+
+ @Test
+ public void testOnUiEntrySelected_pendingIntentWithException_callbackWithCancelledException() {
+ final ArgumentCaptor<String> exceptionTypeCaptor =
+ ArgumentCaptor.forClass(String.class);
+ final ArgumentCaptor<String> exceptionMessageCaptor =
+ ArgumentCaptor.forClass(String.class);
+ final ArgumentCaptor<ComponentName> cpComponentNameCaptor =
+ ArgumentCaptor.forClass(ComponentName.class);
+ final ArgumentCaptor<ProviderSession.Status> statusCaptor =
+ ArgumentCaptor.forClass(ProviderSession.Status.class);
+
+ mProviderRegistryGetSession.invokeSession();
+ GetCredentialProviderData providerData = (GetCredentialProviderData)
+ mProviderRegistryGetSession.prepareUiData();
+ String entryKey = providerData.getCredentialEntries().get(0).getSubkey();
+ final Intent intent = new Intent();
+ final ProviderPendingIntentResponse providerPendingIntentResponse = new
+ ProviderPendingIntentResponse(Activity.RESULT_CANCELED, intent);
+
+ mProviderRegistryGetSession.onUiEntrySelected(
+ ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY,
+ entryKey, providerPendingIntentResponse);
+
+ verify(mGetRequestSession).onProviderStatusChanged(statusCaptor.capture(),
+ cpComponentNameCaptor.capture());
+ assertThat(statusCaptor.getValue()).isEqualTo(ProviderSession.Status.CREDENTIALS_RECEIVED);
+ verify(mGetRequestSession).onFinalErrorReceived(cpComponentNameCaptor.capture(),
+ exceptionTypeCaptor.capture(), exceptionMessageCaptor.capture());
+ assertThat(cpComponentNameCaptor.getValue())
+ .isSameInstanceAs(CREDENTIAL_PROVIDER_COMPONENT);
+ assertThat(exceptionTypeCaptor.getValue())
+ .isEqualTo(GetCredentialException.TYPE_USER_CANCELED);
+ }
+
+ @Test
+ public void testOnUiEntrySelected_correctEntryKeyPendingIntentResponseExists_succeeds() {
+ final ArgumentCaptor<GetCredentialResponse> getCredentialResponseCaptor =
+ ArgumentCaptor.forClass(GetCredentialResponse.class);
+ final ArgumentCaptor<ComponentName> cpComponentNameCaptor =
+ ArgumentCaptor.forClass(ComponentName.class);
+ final ArgumentCaptor<ProviderSession.Status> statusCaptor =
+ ArgumentCaptor.forClass(ProviderSession.Status.class);
+ mProviderRegistryGetSession.invokeSession();
+ GetCredentialProviderData providerData = (GetCredentialProviderData)
+ mProviderRegistryGetSession.prepareUiData();
+ final Intent intent = new Intent();
+ final GetCredentialResponse response =
+ new GetCredentialResponse(new Credential(CREDENTIAL_TYPE, new Bundle()));
+ intent.putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, response);
+ String entryKey = providerData.getCredentialEntries().get(0).getSubkey();
+ final ProviderPendingIntentResponse providerPendingIntentResponse = new
+ ProviderPendingIntentResponse(Activity.RESULT_OK, intent);
+
+ mProviderRegistryGetSession.onUiEntrySelected(
+ ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY,
+ entryKey, providerPendingIntentResponse);
+
+ verify(mGetRequestSession).onProviderStatusChanged(statusCaptor.capture(),
+ cpComponentNameCaptor.capture());
+ assertThat(statusCaptor.getValue()).isEqualTo(ProviderSession.Status.CREDENTIALS_RECEIVED);
+ verify(mGetRequestSession).onFinalResponseReceived(cpComponentNameCaptor.capture(),
+ getCredentialResponseCaptor.capture());
+ assertThat(cpComponentNameCaptor.getValue())
+ .isSameInstanceAs(CREDENTIAL_PROVIDER_COMPONENT);
+ assertThat(getCredentialResponseCaptor.getValue()).isSameInstanceAs(response);
+ }
+
+ private static Slice createSlice() {
+ return new Slice.Builder(Uri.EMPTY, new SliceSpec("", 0)).build();
+ }
+
+ private static CallingAppInfo createCallingAppInfo() throws CertificateException {
+ return new CallingAppInfo(CALLING_PACKAGE_NAME,
+ new SigningInfo(
+ new SigningDetails(new Signature[]{}, 0,
+ null)));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index 85a2446..375b52d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -21,6 +21,7 @@
import static android.app.admin.SystemUpdatePolicy.TYPE_INSTALL_WINDOWED;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import android.content.ComponentName;
import android.os.IpcDataCache;
@@ -43,6 +44,7 @@
@RunWith(AndroidJUnit4.class)
public class OwnersTest extends DpmTestBase {
+ private static final int TEST_PO_USER = 10;
private static final String TESTDPC_PACKAGE = "com.afwsamples.testdpc";
private final DeviceStateCacheImpl mDeviceStateCache = new DeviceStateCacheImpl();
@@ -55,11 +57,11 @@
@Test
public void loadProfileOwner() throws Exception {
- getServices().addUsers(10);
+ getServices().addUsers(TEST_PO_USER);
final Owners owners = makeOwners();
- DpmTestUtils.writeToFile(owners.getProfileOwnerFile(10),
+ DpmTestUtils.writeToFile(owners.getProfileOwnerFile(TEST_PO_USER),
DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/profile_owner_1.xml"));
owners.load();
@@ -71,6 +73,9 @@
assertThat(owners.getProfileOwnerComponent(10))
.isEqualTo(new ComponentName(TESTDPC_PACKAGE,
"com.afwsamples.testdpc.DeviceAdminReceiver"));
+
+ assertWithMessage("Profile owner data in DeviceStateCache wasn't populated")
+ .that(mDeviceStateCache.isUserOrganizationManaged(TEST_PO_USER)).isTrue();
}
@Test
@@ -90,6 +95,10 @@
"com.afwsamples.testdpc.DeviceAdminReceiver"));
assertThat(owners.getSystemUpdatePolicy().getPolicyType()).isEqualTo(TYPE_INSTALL_WINDOWED);
+
+ assertWithMessage("Device owner data in DeviceStateCache wasn't populated")
+ .that(mDeviceStateCache.isUserOrganizationManaged(owners.getDeviceOwnerUserId()))
+ .isTrue();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index de1c219..94d30bb 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -24,6 +24,8 @@
import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
+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.assertNotEquals;
@@ -32,8 +34,10 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -61,10 +65,13 @@
import android.hardware.display.IDisplayManagerCallback;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
+import android.media.projection.IMediaProjectionManager;
+import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.MessageQueue;
import android.os.Process;
+import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
@@ -158,25 +165,40 @@
}
};
- class BasicInjector extends DisplayManagerService.Injector {
- @Override
- VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
- Handler handler, DisplayAdapter.Listener displayAdapterListener) {
- return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
- (String name, boolean secure, float refreshRate) -> mMockDisplayToken);
- }
+ class BasicInjector extends DisplayManagerService.Injector {
+ @Override
+ IMediaProjectionManager getProjectionService() {
+ return mMockProjectionService;
+ }
- @Override
- LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
- Handler handler, DisplayAdapter.Listener displayAdapterListener) {
- return new LocalDisplayAdapter(syncRoot, context, handler,
- displayAdapterListener, new LocalDisplayAdapter.Injector() {
- @Override
- public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
- return mSurfaceControlProxy;
- }
- });
- }
+ @Override
+ VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
+ Handler handler, DisplayAdapter.Listener displayAdapterListener) {
+ return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
+ new VirtualDisplayAdapter.SurfaceControlDisplayFactory() {
+ @Override
+ public IBinder createDisplay(String name, boolean secure,
+ float requestedRefreshRate) {
+ return mMockDisplayToken;
+ }
+
+ @Override
+ public void destroyDisplay(IBinder displayToken) {
+ }
+ });
+ }
+
+ @Override
+ LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
+ Handler handler, DisplayAdapter.Listener displayAdapterListener) {
+ return new LocalDisplayAdapter(syncRoot, context, handler,
+ displayAdapterListener, new LocalDisplayAdapter.Injector() {
+ @Override
+ public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
+ return mSurfaceControlProxy;
+ }
+ });
+ }
@Override
int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
@@ -198,6 +220,7 @@
private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
+ @Mock IMediaProjectionManager mMockProjectionService;
@Mock IVirtualDeviceManager mIVirtualDeviceManager;
@Mock InputManagerInternal mMockInputManagerInternal;
@Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal;
@@ -285,6 +308,7 @@
builder.setFlags(flags);
int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
null /* projection */, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -410,6 +434,7 @@
builder.setUniqueId(uniqueId);
int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
null /* projection */, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -446,6 +471,7 @@
builder.setUniqueId(uniqueId);
int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
/* projection= */ null, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -479,6 +505,7 @@
builder.setUniqueId(uniqueId);
int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
/* projection= */ null, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -720,6 +747,7 @@
builder.setUniqueId(uniqueId);
final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
// The second virtual display requests to mirror the first virtual display.
final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
@@ -731,6 +759,7 @@
final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
mMockAppToken2 /* callback */, null /* projection */,
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
// flush the handler
@@ -768,6 +797,7 @@
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
// Create a second virtual display. This should be added to the previously created display
@@ -783,6 +813,7 @@
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
assertEquals(
@@ -820,6 +851,7 @@
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
// Create a second virtual display. With the flag VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP,
@@ -838,6 +870,7 @@
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
assertNotEquals(
@@ -881,6 +914,7 @@
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
// Check that FLAG_ALWAYS_UNLOCKED is set.
assertNotEquals(
@@ -906,6 +940,7 @@
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
// Check that FLAG_ALWAYS_UNLOCKED is set.
assertNotEquals(
@@ -929,6 +964,7 @@
null /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
// Check that FLAG_ALWAYS_UNLOCKED is not set.
assertEquals(
@@ -960,6 +996,7 @@
.setFlags(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
// The second virtual display requests to mirror the first virtual display.
final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
@@ -971,6 +1008,7 @@
final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
mMockAppToken2 /* callback */, null /* projection */,
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
// flush the handler
@@ -985,6 +1023,54 @@
Display.INVALID_DISPLAY);
}
+ @Test
+ public void testCreateVirtualDisplay_setContentRecordingSessionSuccess() throws Exception {
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ when(mMockWindowManagerInternal
+ .setContentRecordingSession(any(ContentRecordingSession.class)))
+ .thenReturn(true);
+
+ final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+ VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+ builder.setUniqueId("uniqueId --- setContentRecordingSession true");
+ builder.setContentRecordingSession(
+ ContentRecordingSession.createDisplaySession(new Binder("")));
+
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ registerDefaultDisplays(displayManager);
+ displayManager.windowManagerAndInputReady();
+
+ DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+ final int displayId = binderService.createVirtualDisplay(builder.build(),
+ mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+
+ assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+ }
+
+ @Test
+ public void testCreateVirtualDisplay_setContentRecordingSessionFail() throws Exception {
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ when(mMockWindowManagerInternal
+ .setContentRecordingSession(any(ContentRecordingSession.class)))
+ .thenReturn(false);
+
+ final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+ VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+ builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+ builder.setContentRecordingSession(
+ ContentRecordingSession.createDisplaySession(new Binder("")));
+
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ registerDefaultDisplays(displayManager);
+ displayManager.windowManagerAndInputReady();
+
+ DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+ final int displayId = binderService.createVirtualDisplay(builder.build(),
+ mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+
+ assertThat(displayId).isEqualTo(Display.INVALID_DISPLAY);
+ }
+
/**
* Tests that the virtual display is created with
* {@link VirtualDisplayConfig.Builder#setSurface(Surface)}
@@ -1011,6 +1097,7 @@
builder.setUniqueId(uniqueId);
final int displayId = binderService.createVirtualDisplay(builder.build(),
mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -1043,6 +1130,7 @@
int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
null /* projection */, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
@@ -1093,7 +1181,6 @@
registerDefaultDisplays(displayManager);
- DisplayManagerService.BinderService bs = displayManager.new BinderService();
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
@@ -1111,6 +1198,7 @@
int displayId = localService.createVirtualDisplay(builder.build(),
mMockAppToken /* callback */, virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class), PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
index 35a677e..817b245 100644
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -377,6 +377,33 @@
assertTrue(Float.isNaN(mDataStore.getBrightness(testDisplayDevice)));
}
+ @Test
+ public void testStoreAndRestoreBrightnessNitsForDefaultDisplay() {
+ float brightnessNitsForDefaultDisplay = 190;
+ mDataStore.loadIfNeeded();
+ mDataStore.setBrightnessNitsForDefaultDisplay(brightnessNitsForDefaultDisplay);
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ mInjector.setWriteStream(baos);
+ mDataStore.saveIfNeeded();
+ mTestLooper.dispatchAll();
+ assertTrue(mInjector.wasWriteSuccessful());
+ TestInjector newInjector = new TestInjector();
+ PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ newInjector.setReadStream(bais);
+ newDataStore.loadIfNeeded();
+ assertEquals(brightnessNitsForDefaultDisplay,
+ mDataStore.getBrightnessNitsForDefaultDisplay(), 0);
+ assertEquals(brightnessNitsForDefaultDisplay,
+ newDataStore.getBrightnessNitsForDefaultDisplay(), 0);
+ }
+
+ @Test
+ public void testInitialBrightnessNitsForDefaultDisplay() {
+ mDataStore.loadIfNeeded();
+ assertEquals(-1, mDataStore.getBrightnessNitsForDefaultDisplay(), 0);
+ }
public class TestInjector extends PersistentDataStore.Injector {
private InputStream mReadStream;
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index ae05e32..cfb432a 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.res.Resources;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.HandlerExecutor;
import android.os.PowerManager;
@@ -34,6 +35,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.display.AutomaticBrightnessController;
import com.android.server.display.BrightnessSetting;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
@@ -47,7 +49,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class DisplayBrightnessControllerTest {
- private static final int DISPLAY_ID = 1;
+ private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
private static final float DEFAULT_BRIGHTNESS = 0.15f;
@Mock
@@ -55,6 +57,8 @@
@Mock
private Context mContext;
@Mock
+ private Resources mResources;
+ @Mock
private BrightnessSetting mBrightnessSetting;
@Mock
private Runnable mOnBrightnessChangeRunnable;
@@ -67,6 +71,7 @@
@Before
public void before() {
MockitoAnnotations.initMocks(this);
+ when(mContext.getResources()).thenReturn(mResources);
DisplayBrightnessController.Injector injector = new DisplayBrightnessController.Injector() {
@Override
DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(
@@ -75,6 +80,10 @@
}
};
when(mBrightnessSetting.getBrightness()).thenReturn(Float.NaN);
+ when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(-1f);
+ when(mResources.getBoolean(
+ com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay))
+ .thenReturn(true);
mDisplayBrightnessController = new DisplayBrightnessController(mContext, injector,
DISPLAY_ID, DEFAULT_BRIGHTNESS, mBrightnessSetting, mOnBrightnessChangeRunnable,
mBrightnessChangeExecutor);
@@ -82,8 +91,8 @@
@Test
public void testIfFirstScreenBrightnessIsDefault() {
- assertEquals(mDisplayBrightnessController.getCurrentBrightness(), DEFAULT_BRIGHTNESS,
- 0.0f);
+ assertEquals(DEFAULT_BRIGHTNESS, mDisplayBrightnessController.getCurrentBrightness(),
+ /* delta= */ 0.0f);
}
@Test
@@ -123,7 +132,7 @@
float currentScreenBrightness = 0.4f;
mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(currentScreenBrightness);
assertEquals(mDisplayBrightnessController.getCurrentBrightness(),
- currentScreenBrightness, 0.0f);
+ currentScreenBrightness, /* delta= */ 0.0f);
verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
// No change to the current screen brightness is same as the existing one
@@ -136,7 +145,7 @@
float pendingScreenBrightness = 0.4f;
mDisplayBrightnessController.setPendingScreenBrightness(pendingScreenBrightness);
assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
- pendingScreenBrightness, 0.0f);
+ pendingScreenBrightness, /* delta= */ 0.0f);
}
@Test
@@ -158,7 +167,7 @@
verify(temporaryBrightnessStrategy).setTemporaryScreenBrightness(
PowerManager.BRIGHTNESS_INVALID_FLOAT);
assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
- PowerManager.BRIGHTNESS_INVALID_FLOAT, 0.0f);
+ PowerManager.BRIGHTNESS_INVALID_FLOAT, /* delta= */ 0.0f);
// user set brightness is set as expected
currentBrightness = 0.4f;
@@ -169,15 +178,15 @@
mDisplayBrightnessController.setTemporaryBrightness(temporaryScreenBrightness);
assertTrue(mDisplayBrightnessController.updateUserSetScreenBrightness());
assertEquals(mDisplayBrightnessController.getCurrentBrightness(),
- pendingScreenBrightness, 0.0f);
+ pendingScreenBrightness, /* delta= */ 0.0f);
assertEquals(mDisplayBrightnessController.getLastUserSetScreenBrightness(),
- pendingScreenBrightness, 0.0f);
+ pendingScreenBrightness, /* delta= */ 0.0f);
verify(mBrightnessChangeExecutor, times(2))
.execute(mOnBrightnessChangeRunnable);
verify(temporaryBrightnessStrategy, times(2))
.setTemporaryScreenBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT);
assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
- PowerManager.BRIGHTNESS_INVALID_FLOAT, 0.0f);
+ PowerManager.BRIGHTNESS_INVALID_FLOAT, /* delta= */ 0.0f);
}
@Test
@@ -198,20 +207,20 @@
float brightnessSetting = 0.2f;
when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), brightnessSetting,
- 0.0f);
+ /* delta= */ 0.0f);
// getScreenBrightnessSetting value is clamped if BrightnessSetting returns value beyond max
brightnessSetting = 1.1f;
when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), 1.0f,
- 0.0f);
+ /* delta= */ 0.0f);
// getScreenBrightnessSetting returns default value is BrightnessSetting returns invalid
// value.
brightnessSetting = Float.NaN;
when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), DEFAULT_BRIGHTNESS,
- 0.0f);
+ /* delta= */ 0.0f);
}
@Test
@@ -248,6 +257,64 @@
}
@Test
+ public void testBrightnessNitsForDefaultDisplay() {
+ float brightness = 0.3f;
+ float nits = 500;
+ AutomaticBrightnessController automaticBrightnessController =
+ mock(AutomaticBrightnessController.class);
+ when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
+ when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
+
+ mDisplayBrightnessController.setAutomaticBrightnessController(
+ automaticBrightnessController);
+ assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(),
+ /* delta= */ 0);
+
+ float newBrightness = 0.5f;
+ float newNits = 700;
+ when(automaticBrightnessController.convertToNits(newBrightness)).thenReturn(newNits);
+ mDisplayBrightnessController.setBrightness(newBrightness);
+ verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(newNits);
+ }
+
+ @Test
+ public void testConvertToNits() {
+ float brightness = 0.5f;
+ float nits = 300;
+
+ // ABC is null
+ assertEquals(-1f, mDisplayBrightnessController.convertToNits(brightness),
+ /* delta= */ 0);
+
+ AutomaticBrightnessController automaticBrightnessController =
+ mock(AutomaticBrightnessController.class);
+ when(automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+ mDisplayBrightnessController.setAutomaticBrightnessController(
+ automaticBrightnessController);
+
+ assertEquals(nits, mDisplayBrightnessController.convertToNits(brightness), /* delta= */ 0);
+ }
+
+ @Test
+ public void testConvertToFloatScale() {
+ float brightness = 0.5f;
+ float nits = 300;
+
+ // ABC is null
+ assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ mDisplayBrightnessController.convertToFloatScale(nits), /* delta= */ 0);
+
+ AutomaticBrightnessController automaticBrightnessController =
+ mock(AutomaticBrightnessController.class);
+ when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
+ mDisplayBrightnessController.setAutomaticBrightnessController(
+ automaticBrightnessController);
+
+ assertEquals(brightness, mDisplayBrightnessController.convertToFloatScale(nits),
+ /* delta= */ 0);
+ }
+
+ @Test
public void stop() {
BrightnessSetting.BrightnessSettingListener brightnessSettingListener = mock(
BrightnessSetting.BrightnessSettingListener.class);
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
index c0a994b..685e8d6 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
@@ -18,7 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -75,7 +76,7 @@
@Test
public void testDump_emptyPreloadedNanoappList() {
- when(mMockContextHubWrapper.getPreloadedNanoappIds()).thenReturn(null);
+ when(mMockContextHubWrapper.getPreloadedNanoappIds(anyInt())).thenReturn(null);
StringWriter stringWriter = new StringWriter();
ContextHubService service = new ContextHubService(mContext, mMockContextHubWrapper);
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
index 299f153..16a02b6 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
@@ -91,8 +91,16 @@
}
@Test
- public void testQueryException_returnsFalse() {
- contentProvider.setThrowException(true);
+ public void testQuerySQLiteException_returnsFalse() {
+ contentProvider.setThrowSQLiteException(true);
+
+ Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, CONTACT_LOOKUP_KEY);
+ assertFalse(mHelper.query(contactUri.toString()));
+ }
+
+ @Test
+ public void testQueryIllegalArgumentException_returnsFalse() {
+ contentProvider.setThrowIllegalArgumentException(true);
Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, CONTACT_LOOKUP_KEY);
assertFalse(mHelper.query(contactUri.toString()));
@@ -178,14 +186,18 @@
private class ContactsContentProvider extends MockContentProvider {
private Map<Uri, Cursor> mUriPrefixToCursorMap = new ArrayMap<>();
- private boolean throwException = false;
+ private boolean mThrowSQLiteException = false;
+ private boolean mThrowIllegalArgumentException = false;
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
- if (throwException) {
+ if (mThrowSQLiteException) {
throw new SQLiteException();
}
+ if (mThrowIllegalArgumentException) {
+ throw new IllegalArgumentException();
+ }
for (Uri prefixUri : mUriPrefixToCursorMap.keySet()) {
if (uri.isPathPrefixMatch(prefixUri)) {
@@ -195,8 +207,12 @@
return mUriPrefixToCursorMap.get(uri);
}
- public void setThrowException(boolean throwException) {
- this.throwException = throwException;
+ public void setThrowSQLiteException(boolean throwException) {
+ this.mThrowSQLiteException = throwException;
+ }
+
+ public void setThrowIllegalArgumentException(boolean throwException) {
+ this.mThrowIllegalArgumentException = throwException;
}
private void registerCursor(Uri uriPrefix, Cursor cursor) {
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
index 178670e..397d7b5 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
@@ -21,7 +21,6 @@
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI;
import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_REASON_HALF_WINDOW_MS;
-import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_RETENTION_MS;
import static com.google.common.truth.Truth.assertThat;
@@ -66,6 +65,7 @@
public void removesOldWakeups() {
// The xml resource doesn't matter for this test.
final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_1, mHandler);
+ final long retention = obj.mConfig.WAKEUP_STATS_RETENTION_MS;
final Set<Long> timestamps = new HashSet<>();
final long firstWakeup = 453192;
@@ -73,22 +73,21 @@
obj.noteWakeupTimeAndReason(firstWakeup, 32, KERNEL_REASON_UNKNOWN_IRQ);
timestamps.add(firstWakeup);
for (int i = 1; i < 1000; i++) {
- final long delta = mRandom.nextLong(WAKEUP_RETENTION_MS);
+ final long delta = mRandom.nextLong(retention);
if (timestamps.add(firstWakeup + delta)) {
obj.noteWakeupTimeAndReason(firstWakeup + delta, i, KERNEL_REASON_UNKNOWN_IRQ);
}
}
assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size());
- obj.noteWakeupTimeAndReason(firstWakeup + WAKEUP_RETENTION_MS + 1242, 231,
+ obj.noteWakeupTimeAndReason(firstWakeup + retention + 1242, 231,
KERNEL_REASON_UNKNOWN_IRQ);
assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size());
for (int i = 0; i < 100; i++) {
- final long now = mRandom.nextLong(WAKEUP_RETENTION_MS + 1, 100 * WAKEUP_RETENTION_MS);
+ final long now = mRandom.nextLong(retention + 1, 100 * retention);
obj.noteWakeupTimeAndReason(now, i, KERNEL_REASON_UNKNOWN_IRQ);
- assertThat(obj.mWakeupEvents.closestIndexOnOrBefore(now - WAKEUP_RETENTION_MS))
- .isLessThan(0);
+ assertThat(obj.mWakeupEvents.closestIndexOnOrBefore(now - retention)).isLessThan(0);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 56461f0..b80c3e8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -26,6 +26,7 @@
import static android.window.BackNavigationInfo.typeToString;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -149,6 +150,28 @@
}
@Test
+ public void backTypeCrossActivityWithCustomizeExitAnimation() {
+ CrossActivityTestCase testCase = createTopTaskWithTwoActivities();
+ IOnBackInvokedCallback callback = withSystemCallback(testCase.task);
+ testCase.windowFront.mAttrs.windowAnimations = 0x10;
+ spyOn(mDisplayContent.mAppTransition.mTransitionAnimation);
+ doReturn(0xffff00AB).when(mDisplayContent.mAppTransition.mTransitionAnimation)
+ .getAnimationResId(any(), anyInt(), anyInt());
+ doReturn(0xffff00CD).when(mDisplayContent.mAppTransition.mTransitionAnimation)
+ .getDefaultAnimationResId(anyInt(), anyInt());
+
+ BackNavigationInfo backNavigationInfo = startBackNavigation();
+ assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
+ assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
+ assertThat(backNavigationInfo.getCustomAnimationInfo().getWindowAnimations())
+ .isEqualTo(testCase.windowFront.mAttrs.windowAnimations);
+ assertThat(typeToString(backNavigationInfo.getType()))
+ .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
+ // verify if back animation would start.
+ assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation());
+ }
+
+ @Test
public void backTypeCrossActivityWhenBackToPreviousActivity() {
CrossActivityTestCase testCase = createTopTaskWithTwoActivities();
IOnBackInvokedCallback callback = withSystemCallback(testCase.task);
@@ -158,6 +181,8 @@
assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
assertThat(typeToString(backNavigationInfo.getType()))
.isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
+ // verify if back animation would start.
+ assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation());
// reset drawing status
testCase.recordFront.forAllWindows(w -> {
@@ -510,6 +535,8 @@
testCase.task = task;
testCase.recordBack = record1;
testCase.recordFront = record2;
+ testCase.windowBack = window1;
+ testCase.windowFront = window2;
return testCase;
}
@@ -525,6 +552,8 @@
private class CrossActivityTestCase {
public Task task;
public ActivityRecord recordBack;
+ public WindowState windowBack;
public ActivityRecord recordFront;
+ public WindowState windowFront;
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 3379beb..d071f13 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -34,7 +34,6 @@
import static android.view.Display.INVALID_DISPLAY;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
import static android.view.DisplayCutout.fromBoundingRect;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
@@ -1557,15 +1556,16 @@
assertFalse(mNotificationShadeWindow.isAnimating(PARENTS, ANIMATION_TYPE_TOKEN_TRANSFORM));
// If the visibility of insets state is changed, the rotated state should be updated too.
+ final int statusBarId = mStatusBarWindow.getControllableInsetProvider().getSource().getId();
final InsetsState rotatedState = app.getFixedRotationTransformInsetsState();
final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
- assertEquals(state.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()),
- rotatedState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
- state.setSourceVisible(ITYPE_STATUS_BAR,
- !rotatedState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
+ assertEquals(state.isSourceOrDefaultVisible(statusBarId, statusBars()),
+ rotatedState.isSourceOrDefaultVisible(statusBarId, statusBars()));
+ state.setSourceVisible(statusBarId,
+ !rotatedState.isSourceOrDefaultVisible(statusBarId, statusBars()));
mDisplayContent.getInsetsStateController().notifyInsetsChanged();
- assertEquals(state.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()),
- rotatedState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
+ assertEquals(state.isSourceOrDefaultVisible(statusBarId, statusBars()),
+ rotatedState.isSourceOrDefaultVisible(statusBarId, statusBars()));
final Rect outFrame = new Rect();
final Rect outInsets = new Rect();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 6656f4c..695a72e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -16,37 +16,32 @@
package com.android.server.wm;
-import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_GESTURES;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy;
-import static org.testng.Assert.expectThrows;
-import android.graphics.Insets;
import android.graphics.Rect;
+import android.os.Binder;
import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
import android.view.DisplayInfo;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.PrivacyIndicatorBounds;
import android.view.RoundedCorners;
+import android.view.WindowInsets;
import androidx.test.filters.SmallTest;
@@ -151,74 +146,23 @@
public void addingWindow_withInsetsTypes() {
mDisplayPolicy.removeWindowLw(mStatusBarWindow); // Removes the existing one.
- WindowState win = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
+ final WindowState win = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "statusBar");
+ final Binder owner = new Binder();
win.mAttrs.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(ITYPE_STATUS_BAR),
- new InsetsFrameProvider(ITYPE_TOP_GESTURES)
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars()),
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.systemGestures())
};
- win.getFrame().set(0, 0, 500, 100);
-
addWindow(win);
- win.updateSourceFrame(win.getFrame());
- InsetsStateController controller = mDisplayContent.getInsetsStateController();
- controller.onPostLayout();
-
- InsetsSourceProvider statusBarProvider = controller.peekSourceProvider(ITYPE_STATUS_BAR);
- assertEquals(new Rect(0, 0, 500, 100), statusBarProvider.getSource().getFrame());
- assertEquals(Insets.of(0, 100, 0, 0),
- statusBarProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
- false /* ignoreVisibility */));
-
- InsetsSourceProvider topGesturesProvider = controller.peekSourceProvider(
- ITYPE_TOP_GESTURES);
- assertEquals(new Rect(0, 0, 500, 100), topGesturesProvider.getSource().getFrame());
- assertEquals(Insets.of(0, 100, 0, 0),
- topGesturesProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
- false /* ignoreVisibility */));
-
- InsetsSourceProvider navigationBarProvider = controller.peekSourceProvider(
- ITYPE_NAVIGATION_BAR);
- assertNotEquals(new Rect(0, 0, 500, 100), navigationBarProvider.getSource().getFrame());
- }
-
- @Test
- public void addingWindow_InWindowTypeWithPredefinedInsets() {
- mDisplayPolicy.removeWindowLw(mStatusBarWindow); // Removes the existing one.
- WindowState win = createWindow(null, TYPE_STATUS_BAR, "StatusBar");
- win.mAttrs.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(ITYPE_STATUS_BAR)
- };
win.getFrame().set(0, 0, 500, 100);
-
- addWindow(win);
win.updateSourceFrame(win.getFrame());
mDisplayContent.getInsetsStateController().onPostLayout();
- InsetsSourceProvider provider =
- mDisplayContent.getInsetsStateController().peekSourceProvider(ITYPE_STATUS_BAR);
- // In the new flexible insets setup, the insets frame should always respect the window
- // layout result.
- assertEquals(new Rect(0, 0, 500, 100), provider.getSource().getFrame());
- }
-
- @Test
- public void addingWindow_throwsException_WithMultipleInsetTypes() {
- WindowState win1 = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
- win1.mAttrs.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(ITYPE_STATUS_BAR),
- new InsetsFrameProvider(ITYPE_NAVIGATION_BAR)
- };
-
- expectThrows(IllegalArgumentException.class, () -> addWindow(win1));
-
- WindowState win2 = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
-
- win2.mAttrs.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(ITYPE_CLIMATE_BAR),
- new InsetsFrameProvider(ITYPE_EXTRA_NAVIGATION_BAR)
- };
-
- expectThrows(IllegalArgumentException.class, () -> addWindow(win2));
+ assertTrue(win.hasInsetsSourceProvider());
+ final SparseArray<InsetsSourceProvider> providers = win.getInsetsSourceProviders();
+ for (int i = providers.size() - 1; i >= 0; i--) {
+ final InsetsSourceProvider provider = providers.valueAt(i);
+ assertEquals(new Rect(0, 0, 500, 100), provider.getSource().getFrame());
+ }
}
/**
@@ -272,9 +216,10 @@
.rotationForActivityInDifferentOrientation(eq(mWindow.mActivityRecord));
mWindow.mAboveInsetsState.set(
mDisplayContent.getInsetsStateController().getRawInsetsState());
- final Rect frame = mWindow.getInsetsState().peekSource(ITYPE_STATUS_BAR).getFrame();
+ final int statusBarId = mStatusBarWindow.getControllableInsetProvider().getSource().getId();
+ final Rect frame = mWindow.getInsetsState().peekSource(statusBarId).getFrame();
mDisplayContent.rotateInDifferentOrientationIfNeeded(mWindow.mActivityRecord);
- final Rect rotatedFrame = mWindow.getInsetsState().peekSource(ITYPE_STATUS_BAR).getFrame();
+ final Rect rotatedFrame = mWindow.getInsetsState().peekSource(statusBarId).getFrame();
assertEquals(DISPLAY_WIDTH, frame.width());
assertEquals(DISPLAY_HEIGHT, rotatedFrame.width());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index c694707..20d410c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -18,8 +18,6 @@
import static android.view.DisplayCutout.NO_CUTOUT;
import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.RoundedCorners.NO_ROUNDED_CORNERS;
import static android.view.Surface.ROTATION_0;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
@@ -180,7 +178,8 @@
mDisplayContent.setLayoutNeeded();
mDisplayContent.performLayout(true /* initial */, false /* updateImeWindows */);
- final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR, navigationBars());
+ final InsetsSource navSource = new InsetsSource(
+ InsetsSource.createId(null, 0, navigationBars()), navigationBars());
navSource.setFrame(mNavBarWindow.getFrame());
opaqueDarkNavBar.mAboveInsetsState.addSource(navSource);
opaqueLightNavBar.mAboveInsetsState.addSource(navSource);
@@ -250,15 +249,16 @@
@Test
public void testOverlappingWithNavBar() {
- final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR, navigationBars());
+ final InsetsSource navSource = new InsetsSource(
+ InsetsSource.createId(null, 0, navigationBars()), navigationBars());
navSource.setFrame(new Rect(100, 200, 200, 300));
testOverlappingWithNavBarType(navSource);
}
@Test
public void testOverlappingWithExtraNavBar() {
- final InsetsSource navSource =
- new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR, navigationBars());
+ final InsetsSource navSource = new InsetsSource(
+ InsetsSource.createId(null, 1, navigationBars()), navigationBars());
navSource.setFrame(new Rect(100, 200, 200, 300));
testOverlappingWithNavBarType(navSource);
}
@@ -331,7 +331,8 @@
displayPolicy.layoutWindowLw(mImeWindow, null, mDisplayContent.mDisplayFrames);
final InsetsSource imeSource = state.peekSource(ID_IME);
- final InsetsSource navBarSource = state.peekSource(ITYPE_NAVIGATION_BAR);
+ final InsetsSource navBarSource = state.peekSource(
+ mNavBarWindow.getControllableInsetProvider().getSource().getId());
assertNotNull(imeSource);
assertNotNull(navBarSource);
@@ -358,7 +359,8 @@
displayPolicy.layoutWindowLw(mNavBarWindow, null, mDisplayContent.mDisplayFrames);
final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
- final InsetsSource navBarSource = state.peekSource(ITYPE_NAVIGATION_BAR);
+ final InsetsSource navBarSource = state.peekSource(
+ mNavBarWindow.getControllableInsetProvider().getSource().getId());
assertEquals(attrs.height - 10, navBarSource.getFrame().height());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 1a126cf..2065540 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -19,12 +19,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
-import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
-import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
@@ -51,6 +45,7 @@
import static org.mockito.Mockito.verify;
import android.app.StatusBarManager;
+import android.os.Binder;
import android.platform.test.annotations.Presubmit;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
@@ -158,7 +153,7 @@
@Test
public void testControlsForDispatch_remoteInsetsControllerControlsBars_appHasNoControl() {
mDisplayContent.setRemoteInsetsController(createDisplayWindowInsetsController());
- mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(true);
+ mDisplayContent.getDisplayPolicy().setRemoteInsetsControllerControlsSystemBars(true);
addStatusBar();
addNavigationBar();
@@ -261,11 +256,15 @@
@Test
public void testShowTransientBars_bothCanBeTransient_appGetsBothFakeControls() {
final WindowState statusBar = addStatusBar();
+ final InsetsSourceProvider statusBarProvider = statusBar.getControllableInsetProvider();
+ final int statusBarId = statusBarProvider.getSource().getId();
statusBar.setHasSurface(true);
- statusBar.getControllableInsetProvider().setServerVisible(true);
+ statusBarProvider.setServerVisible(true);
final WindowState navBar = addNavigationBar();
+ final InsetsSourceProvider navBarProvider = statusBar.getControllableInsetProvider();
+ final int navBarId = statusBarProvider.getSource().getId();
navBar.setHasSurface(true);
- navBar.getControllableInsetProvider().setServerVisible(true);
+ navBarProvider.setServerVisible(true);
final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
spyOn(policy);
doNothing().when(policy).startAnimation(anyBoolean(), any());
@@ -276,9 +275,9 @@
policy.updateBarControlTarget(mAppWindow);
waitUntilWindowAnimatorIdle();
assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState()
- .isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
+ .isSourceOrDefaultVisible(statusBarId, statusBars()));
assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState()
- .isSourceOrDefaultVisible(ITYPE_NAVIGATION_BAR, navigationBars()));
+ .isSourceOrDefaultVisible(navBarId, navigationBars()));
policy.showTransient(navigationBars() | statusBars(), true /* isGestureOnSystemBar */);
waitUntilWindowAnimatorIdle();
@@ -292,9 +291,9 @@
}
assertTrue(mDisplayContent.getInsetsStateController().getRawInsetsState()
- .isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
+ .isSourceOrDefaultVisible(statusBarId, statusBars()));
assertTrue(mDisplayContent.getInsetsStateController().getRawInsetsState()
- .isSourceOrDefaultVisible(ITYPE_NAVIGATION_BAR, navigationBars()));
+ .isSourceOrDefaultVisible(navBarId, navigationBars()));
}
@SetupWindows(addWindows = W_ACTIVITY)
@@ -356,16 +355,16 @@
}
final InsetsState state = mAppWindow.getInsetsState();
- state.setSourceVisible(ITYPE_STATUS_BAR, true);
- state.setSourceVisible(ITYPE_NAVIGATION_BAR, true);
+ state.setSourceVisible(statusBarSource.getId(), true);
+ state.setSourceVisible(navBarSource.getId(), true);
final InsetsState clientState = mAppWindow.getInsetsState();
// The transient bar states for client should be invisible.
- assertFalse(clientState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
- assertFalse(clientState.isSourceOrDefaultVisible(ITYPE_NAVIGATION_BAR, navigationBars()));
+ assertFalse(clientState.isSourceOrDefaultVisible(statusBarSource.getId(), statusBars()));
+ assertFalse(clientState.isSourceOrDefaultVisible(navBarSource.getId(), navigationBars()));
// The original state shouldn't be modified.
- assertTrue(state.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
- assertTrue(state.isSourceOrDefaultVisible(ITYPE_NAVIGATION_BAR, navigationBars()));
+ assertTrue(state.isSourceOrDefaultVisible(statusBarSource.getId(), statusBars()));
+ assertTrue(state.isSourceOrDefaultVisible(navBarSource.getId(), navigationBars()));
mAppWindow.setRequestedVisibleTypes(
navigationBars() | statusBars(), navigationBars() | statusBars());
@@ -402,24 +401,26 @@
}
private WindowState addNavigationBar() {
+ final Binder owner = new Binder();
final WindowState win = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
win.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
win.mAttrs.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(ITYPE_NAVIGATION_BAR),
- new InsetsFrameProvider(ITYPE_BOTTOM_MANDATORY_GESTURES),
- new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT)
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars()),
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.tappableElement()),
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures())
};
mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs);
return win;
}
private WindowState addStatusBar() {
+ final Binder owner = new Binder();
final WindowState win = createWindow(null, TYPE_STATUS_BAR, "statusBar");
win.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
win.mAttrs.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(ITYPE_STATUS_BAR),
- new InsetsFrameProvider(ITYPE_TOP_TAPPABLE_ELEMENT),
- new InsetsFrameProvider(ITYPE_TOP_MANDATORY_GESTURES)
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars()),
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.tappableElement()),
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures())
};
mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs);
return win;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index abbd397..65c7125 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -28,9 +28,6 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
-import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
@@ -88,6 +85,7 @@
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.Binder;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
@@ -95,6 +93,7 @@
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsState;
+import android.view.WindowInsets;
import android.view.WindowManager;
import androidx.test.filters.MediumTest;
@@ -4075,14 +4074,15 @@
TYPE_STATUS_BAR, displayContent);
final WindowManager.LayoutParams attrs =
new WindowManager.LayoutParams(TYPE_STATUS_BAR);
+ final Binder owner = new Binder();
attrs.gravity = android.view.Gravity.TOP;
attrs.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
attrs.setFitInsetsTypes(0 /* types */);
attrs.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(ITYPE_STATUS_BAR),
- new InsetsFrameProvider(ITYPE_TOP_TAPPABLE_ELEMENT),
- new InsetsFrameProvider(ITYPE_TOP_MANDATORY_GESTURES)
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars()),
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.tappableElement()),
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures())
};
final TestWindowState statusBar = new TestWindowState(
displayContent.mWmService, mock(Session.class), new TestIWindow(), attrs, token);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 2bfc5ec..739737e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -30,10 +30,6 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.util.DisplayMetrics.DENSITY_DEFAULT;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.window.DisplayAreaOrganizer.FEATURE_RUNTIME_TASK_CONTAINER_FIRST;
import static com.android.server.wm.ActivityStarter.Request;
@@ -55,6 +51,7 @@
import android.os.Build;
import android.platform.test.annotations.Presubmit;
import android.view.Gravity;
+import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.WindowInsets;
@@ -1916,22 +1913,24 @@
final int st = stableFrame.top;
final int sr = stableFrame.right;
final int sb = stableFrame.bottom;
+ final @WindowInsets.Type.InsetsType int statusBarType = WindowInsets.Type.statusBars();
+ final @WindowInsets.Type.InsetsType int navBarType = WindowInsets.Type.navigationBars();
state.setDisplayFrame(displayFrame);
if (sl > dl) {
- state.getOrCreateSource(ITYPE_CLIMATE_BAR, WindowInsets.Type.statusBars())
+ state.getOrCreateSource(InsetsSource.createId(null, 0, statusBarType), statusBarType)
.setFrame(dl, dt, sl, db);
}
if (st > dt) {
- state.getOrCreateSource(ITYPE_STATUS_BAR, WindowInsets.Type.statusBars())
+ state.getOrCreateSource(InsetsSource.createId(null, 1, statusBarType), statusBarType)
.setFrame(dl, dt, dr, st);
}
if (sr < dr) {
- state.getOrCreateSource(ITYPE_EXTRA_NAVIGATION_BAR, WindowInsets.Type.navigationBars())
+ state.getOrCreateSource(InsetsSource.createId(null, 0, navBarType), navBarType)
.setFrame(sr, dt, dr, db);
}
if (sb < db) {
- state.getOrCreateSource(ITYPE_NAVIGATION_BAR, WindowInsets.Type.navigationBars())
+ state.getOrCreateSource(InsetsSource.createId(null, 1, navBarType), navBarType)
.setFrame(dl, sb, dr, db);
}
// Recompute config and push to children.
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
index 7d13de8..ef20f2b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -45,7 +44,8 @@
@RunWith(WindowTestRunner.class)
public class WindowContainerInsetsSourceProviderTest extends WindowTestsBase {
- private InsetsSource mSource = new InsetsSource(ITYPE_STATUS_BAR, statusBars());
+ private InsetsSource mSource = new InsetsSource(
+ InsetsSource.createId(null, 0, statusBars()), statusBars());
private WindowContainerInsetsSourceProvider mProvider;
private InsetsSource mImeSource = new InsetsSource(ID_IME, ime());
private WindowContainerInsetsSourceProvider mImeProvider;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 169586e..d7e4c55 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1023,7 +1023,7 @@
RunningTaskInfo mInfo;
@Override
- public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { }
+ public void addStartingWindow(StartingWindowInfo info) { }
@Override
public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) { }
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 33067df..b48fd7d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -21,8 +21,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
@@ -425,15 +423,16 @@
final WindowState app = mAppWindow;
statusBar.mHasSurface = true;
assertTrue(statusBar.isVisible());
+ final int statusBarId = InsetsSource.createId(null, 0, statusBars());
mDisplayContent.getInsetsStateController()
- .getOrCreateSourceProvider(ITYPE_STATUS_BAR, statusBars())
+ .getOrCreateSourceProvider(statusBarId, statusBars())
.setWindowContainer(statusBar, null /* frameProvider */,
null /* imeFrameProvider */);
mDisplayContent.getInsetsStateController().onBarControlTargetChanged(
app, null /* fakeTopControlling */, app, null /* fakeNavControlling */);
app.setRequestedVisibleTypes(0, statusBars());
mDisplayContent.getInsetsStateController()
- .getOrCreateSourceProvider(ITYPE_STATUS_BAR, statusBars())
+ .getOrCreateSourceProvider(statusBarId, statusBars())
.updateClientVisibility(app);
waitUntilHandlersIdle();
assertFalse(statusBar.isVisible());
@@ -1004,21 +1003,18 @@
@SetupWindows(addWindows = { W_INPUT_METHOD, W_ACTIVITY })
@Test
public void testImeAlwaysReceivesVisibleNavigationBarInsets() {
- final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR, navigationBars());
+ final int navId = InsetsSource.createId(null, 0, navigationBars());
+ final InsetsSource navSource = new InsetsSource(navId, navigationBars());
mImeWindow.mAboveInsetsState.addSource(navSource);
mAppWindow.mAboveInsetsState.addSource(navSource);
navSource.setVisible(false);
- assertTrue(mImeWindow.getInsetsState().isSourceOrDefaultVisible(
- ITYPE_NAVIGATION_BAR, navigationBars()));
- assertFalse(mAppWindow.getInsetsState().isSourceOrDefaultVisible(
- ITYPE_NAVIGATION_BAR, navigationBars()));
+ assertTrue(mImeWindow.getInsetsState().isSourceOrDefaultVisible(navId, navigationBars()));
+ assertFalse(mAppWindow.getInsetsState().isSourceOrDefaultVisible(navId, navigationBars()));
navSource.setVisible(true);
- assertTrue(mImeWindow.getInsetsState().isSourceOrDefaultVisible(
- ITYPE_NAVIGATION_BAR, navigationBars()));
- assertTrue(mAppWindow.getInsetsState().isSourceOrDefaultVisible(
- ITYPE_NAVIGATION_BAR, navigationBars()));
+ assertTrue(mImeWindow.getInsetsState().isSourceOrDefaultVisible(navId, navigationBars()));
+ assertTrue(mAppWindow.getInsetsState().isSourceOrDefaultVisible(navId, navigationBars()));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 323894ca..ce6cd90 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -26,12 +26,6 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.os.Process.SYSTEM_UID;
-import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
-import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
-import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
@@ -99,6 +93,7 @@
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.View;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
import android.view.inputmethod.ImeTracker;
@@ -312,7 +307,7 @@
beforeCreateTestDisplay();
mDisplayContent = createNewDisplayWithImeSupport(DISPLAY_IME_POLICY_LOCAL);
addCommonWindows(annotation.addAllCommonWindows(), annotation.addWindows());
- mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(false);
+ mDisplayContent.getDisplayPolicy().setRemoteInsetsControllerControlsSystemBars(false);
// Adding a display will cause freezing the display. Make sure to wait until it's
// unfrozen to not run into race conditions with the tests.
@@ -338,10 +333,11 @@
mStatusBarWindow.mAttrs.layoutInDisplayCutoutMode =
LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mStatusBarWindow.mAttrs.setFitInsetsTypes(0);
+ final IBinder owner = new Binder();
mStatusBarWindow.mAttrs.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(ITYPE_STATUS_BAR),
- new InsetsFrameProvider(ITYPE_TOP_TAPPABLE_ELEMENT),
- new InsetsFrameProvider(ITYPE_TOP_MANDATORY_GESTURES)
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars()),
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.tappableElement()),
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures())
};
}
if (addAll || ArrayUtils.contains(requestedWindows, W_NOTIFICATION_SHADE)) {
@@ -358,14 +354,15 @@
LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mNavBarWindow.mAttrs.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
+ final IBinder owner = new Binder();
mNavBarWindow.mAttrs.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(ITYPE_NAVIGATION_BAR),
- new InsetsFrameProvider(ITYPE_BOTTOM_MANDATORY_GESTURES),
- new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT)
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars()),
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.tappableElement()),
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures())
};
for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
mNavBarWindow.mAttrs.paramsForRotation[rot] =
- getNavBarLayoutParamsForRotation(rot);
+ getNavBarLayoutParamsForRotation(rot, owner);
}
}
if (addAll || ArrayUtils.contains(requestedWindows, W_DOCK_DIVIDER)) {
@@ -388,7 +385,8 @@
}
}
- private WindowManager.LayoutParams getNavBarLayoutParamsForRotation(int rotation) {
+ private WindowManager.LayoutParams getNavBarLayoutParamsForRotation(
+ int rotation, IBinder owner) {
int width = WindowManager.LayoutParams.MATCH_PARENT;
int height = WindowManager.LayoutParams.MATCH_PARENT;
int gravity = Gravity.BOTTOM;
@@ -417,9 +415,9 @@
WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
lp.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(ITYPE_NAVIGATION_BAR),
- new InsetsFrameProvider(ITYPE_BOTTOM_MANDATORY_GESTURES),
- new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT)
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars()),
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.tappableElement()),
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures())
};
return lp;
}
@@ -454,8 +452,10 @@
WindowState createNavBarWithProvidedInsets(DisplayContent dc) {
final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, dc, "navbar");
+ final Binder owner = new Binder();
navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(ITYPE_NAVIGATION_BAR, Insets.of(0, 0, 0, NAV_BAR_HEIGHT))
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars())
+ .setInsetsSize(Insets.of(0, 0, 0, NAV_BAR_HEIGHT))
};
dc.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
return navbar;
@@ -1583,10 +1583,10 @@
}
@Override
- public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
+ public void addStartingWindow(StartingWindowInfo info) {
synchronized (mWMService.mGlobalLock) {
final ActivityRecord activity = mWMService.mRoot.getActivityRecord(
- appToken);
+ info.appToken);
IWindow iWindow = mock(IWindow.class);
doReturn(mock(IBinder.class)).when(iWindow).asBinder();
final WindowState window = WindowTestsBase.createWindow(null,
@@ -1596,8 +1596,8 @@
iWindow,
mPowerManagerWrapper);
activity.mStartingWindow = window;
- mAppWindowMap.put(appToken, window);
- mTaskAppMap.put(info.taskInfo.taskId, appToken);
+ mAppWindowMap.put(info.appToken, window);
+ mTaskAppMap.put(info.taskInfo.taskId, info.appToken);
}
if (mRunnableWhenAddingSplashScreen != null) {
mRunnableWhenAddingSplashScreen.run();
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 918ae79..bf12b9c 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -7608,6 +7608,55 @@
public static final String KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL =
KEY_PREFIX + "emergency_requires_volte_enabled_bool";
+ /**
+ * This values indicates that the cross SIM redialing timer shall be disabled.
+ *
+ * @see #KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT
+ * @see #KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT
+ * @hide
+ */
+ public static final int REDIAL_TIMER_DISABLED = 0;
+
+ /**
+ * A timer to guard the max attempting time on current SIM slot so that modem will not
+ * stuck in current SIM slot for long time. On timer expiry, if emergency call on the
+ * other SIM slot is preferable, telephony shall cancel the emergency call and place the
+ * call on the other SIM slot. If this value is set to {@link #REDIAL_TIMER_DISABLED}, then
+ * the timer will never be started and domain selection continues on the current SIM slot.
+ * This value should be greater than the value of {@link #KEY_EMERGENCY_SCAN_TIMER_SEC_INT}.
+ *
+ * The default value for the timer is 120 seconds.
+ * @hide
+ */
+ public static final String KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT =
+ KEY_PREFIX + "cross_stack_redial_timer_sec_int";
+
+ /**
+ * If emergency calls are only allowed with normal-registered service and UE should get
+ * normal service in a short time with acquired band information, telephony
+ * expects dialing emergency call will be completed in a short time.
+ * If dialing is not completed with in a certain timeout, telephony shall place on
+ * another SIM slot. If this value is set to {@link #REDIAL_TIMER_DISABLED}, then the timer
+ * will never be started and domain selection continues on the current SIM slot.
+ * The timer shall be started for the first trial of each subscription and shall be ignored
+ * in the roaming networks and non-domestic networks.
+ *
+ * The default value for the timer is {@link #REDIAL_TIMER_DISABLED}.
+ * @hide
+ */
+ public static final String KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT =
+ KEY_PREFIX + "quick_cross_stack_redial_timer_sec_int";
+
+ /**
+ * Indicates whether the quick cross stack redial timer will be triggered only when
+ * the device is registered to the network.
+ *
+ * The default value is {@code true}.
+ * @hide
+ */
+ public static final String KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL =
+ KEY_PREFIX + "start_quick_cross_stack_redial_timer_when_registered_bool";
+
private static PersistableBundle getDefaults() {
PersistableBundle defaults = new PersistableBundle();
defaults.putBoolean(KEY_RETRY_EMERGENCY_ON_IMS_PDN_BOOL, false);
@@ -7674,6 +7723,10 @@
defaults.putBoolean(KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL, false);
defaults.putStringArray(KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY,
new String[0]);
+ defaults.putInt(KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT, 120);
+ defaults.putInt(KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT, REDIAL_TIMER_DISABLED);
+ defaults.putBoolean(KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL,
+ true);
return defaults;
}
diff --git a/telephony/java/android/telephony/PreciseDisconnectCause.java b/telephony/java/android/telephony/PreciseDisconnectCause.java
index 3b4cf75..1cfd22c 100644
--- a/telephony/java/android/telephony/PreciseDisconnectCause.java
+++ b/telephony/java/android/telephony/PreciseDisconnectCause.java
@@ -235,6 +235,23 @@
/** Call failed/dropped because of a network detach. */
public static final int NETWORK_DETACH = 261;
+ /**
+ * Dialing emergency calls is currently unavailable.
+ * The call should be redialed on the other subscription silently.
+ * If there is no other subscription available, the call may be redialed
+ * on this subscription again.
+ * @hide
+ */
+ public static final int EMERGENCY_TEMP_FAILURE = 325;
+ /**
+ * Dialing emergency calls is currently unavailable.
+ * The call should be redialed on the other subscription silently.
+ * Even if there is no other subscription available, the call should not
+ * be redialed on this subscription again.
+ * @hide
+ */
+ public static final int EMERGENCY_PERM_FAILURE = 326;
+
/** Mobile station (MS) is locked until next power cycle. */
public static final int CDMA_LOCKED_UNTIL_POWER_CYCLE = 1000;
/** Drop call. */
diff --git a/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl
index c15374a..2954c2d 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl
@@ -16,9 +16,10 @@
package android.telephony.satellite;
-import android.telephony.satellite.ISatelliteDatagramReceiverAck;
import android.telephony.satellite.SatelliteDatagram;
+import com.android.internal.telephony.ILongConsumer;
+
/**
* Interface for satellite datagrams callback.
* @hide
@@ -30,10 +31,10 @@
* @param datagramId An id that uniquely identifies incoming datagram.
* @param datagram Datagram received from satellite.
* @param pendingCount Number of datagrams yet to be received from satellite.
- * @param callback This callback will be used by datagram receiver app to send ack back to
- * Telephony. If the callback is not received within five minutes,
- * Telephony will resend the datagrams.
+ * @param callback This callback will be used by datagram receiver app to send received
+ * datagramId to Telephony. If the callback is not received within five minutes,
+ * Telephony will resend the datagram.
*/
void onSatelliteDatagramReceived(long datagramId, in SatelliteDatagram datagram,
- int pendingCount, ISatelliteDatagramReceiverAck callback);
+ int pendingCount, ILongConsumer callback);
}
diff --git a/telephony/java/android/telephony/satellite/ISatelliteDatagramReceiverAck.aidl b/telephony/java/android/telephony/satellite/ISatelliteDatagramReceiverAck.aidl
deleted file mode 100644
index eeb0ac5..0000000
--- a/telephony/java/android/telephony/satellite/ISatelliteDatagramReceiverAck.aidl
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2023 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.telephony.satellite;
-
-import android.telephony.satellite.PointingInfo;
-import android.telephony.satellite.SatelliteDatagram;
-
-/**
- * Interface for satellite datagram receiver acknowledgement.
- * @hide
- */
-oneway interface ISatelliteDatagramReceiverAck {
- /**
- * This callback will be used by datagram receiver app to send ack back to
- * Telephony. If the callback is not received within five minutes,
- * then Telephony will resend the datagram again.
- *
- * @param datagramId An id that uniquely identifies datagram
- * received by satellite datagram receiver app.
- * This should match with datagramId passed in
- * {@link SatelliteDatagramCallback#onSatelliteDatagramReceived(
- * long, SatelliteDatagram, int, ISatelliteDatagramReceiverAck)}.
- * Upon receiving the ack, Telephony will remove the datagram from
- * the persistent memory.
- */
- void acknowledgeSatelliteDatagramReceived(in long datagramId);
-}
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
index 8ccc993..213b985 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
@@ -19,6 +19,8 @@
import android.annotation.NonNull;
import android.os.Binder;
+import com.android.internal.telephony.ILongConsumer;
+
import java.util.concurrent.Executor;
/**
@@ -38,8 +40,9 @@
}
@Override
- public void onSatelliteDatagramReceived(long datagramId, SatelliteDatagram datagram,
- int pendingCount, ISatelliteDatagramReceiverAck callback) {
+ public void onSatelliteDatagramReceived(long datagramId,
+ @NonNull SatelliteDatagram datagram, int pendingCount,
+ @NonNull ILongConsumer callback) {
final long callingIdentity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> mLocalCallback.onSatelliteDatagramReceived(datagramId,
@@ -59,17 +62,18 @@
* @param datagramId An id that uniquely identifies incoming datagram.
* @param datagram Datagram to be received over satellite.
* @param pendingCount Number of datagrams yet to be received by the app.
- * @param callback This callback will be used by datagram receiver app to send ack back to
- * Telephony.
+ * @param callback This callback will be used by datagram receiver app to send received
+ * datagramId to Telephony. If the callback is not received within five minutes,
+ * Telephony will resend the datagram.
*/
- public void onSatelliteDatagramReceived(long datagramId, SatelliteDatagram datagram,
- int pendingCount, ISatelliteDatagramReceiverAck callback) {
+ public void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram,
+ int pendingCount, @NonNull ILongConsumer callback) {
// Base Implementation
}
/** @hide */
@NonNull
- public final ISatelliteDatagramCallback getBinder() {
+ final ISatelliteDatagramCallback getBinder() {
return mBinder;
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 64454cc..248d9df 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -166,13 +166,6 @@
public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility";
/**
- * Bundle key to get the respoonse from {@link
- * #sendSatelliteDatagram(long, int, SatelliteDatagram, boolean, Executor, OutcomeReceiver)}.
- * @hide
- */
- public static final String KEY_SEND_SATELLITE_DATAGRAM = "send_satellite_datagram";
-
- /**
* The request was successfully processed.
*/
public static final int SATELLITE_ERROR_NONE = 0;
@@ -239,6 +232,8 @@
public static final int SATELLITE_SERVICE_PROVISION_IN_PROGRESS = 14;
/**
* The ongoing request was aborted by either the satellite modem or the network.
+ * This error is also returned when framework decides to abort current send request as one
+ * of the previous send request failed.
*/
public static final int SATELLITE_REQUEST_ABORTED = 15;
/**
@@ -724,24 +719,24 @@
public @interface SatelliteModemState {}
/**
+ * Datagram type is unknown. This generic datagram type should be used only when the
+ * datagram type cannot be mapped to other specific datagram types.
+ */
+ public static final int DATAGRAM_TYPE_UNKNOWN = 0;
+ /**
* Datagram type indicating that the datagram to be sent or received is of type SOS message.
*/
- public static final int DATAGRAM_TYPE_SOS_MESSAGE = 0;
+ public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1;
/**
* Datagram type indicating that the datagram to be sent or received is of type
* location sharing.
*/
- public static final int DATAGRAM_TYPE_LOCATION_SHARING = 1;
- /**
- * Datagram type is unknown. This generic datagram type should be used only when the
- * datagram type cannot be mapped to other specific datagram types.
- */
- public static final int DATAGRAM_TYPE_UNKNOWN = -1;
+ public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2;
@IntDef(prefix = "DATAGRAM_TYPE_", value = {
+ DATAGRAM_TYPE_UNKNOWN,
DATAGRAM_TYPE_SOS_MESSAGE,
- DATAGRAM_TYPE_LOCATION_SHARING,
- DATAGRAM_TYPE_UNKNOWN
+ DATAGRAM_TYPE_LOCATION_SHARING
})
@Retention(RetentionPolicy.SOURCE)
public @interface DatagramType {}
@@ -1268,7 +1263,6 @@
* input to this method. Datagram received here will be passed down to modem without any
* encoding or encryption.
*
- * @param datagramId An id that uniquely identifies datagram requested to be sent.
* @param datagramType datagram type indicating whether the datagram is of type
* SOS_SMS or LOCATION_SHARING.
* @param datagram encoded gateway datagram which is encrypted by the caller.
@@ -1283,51 +1277,32 @@
* user activity and the application's ability to determine the
* best possible UX experience for the user.
* @param executor The executor on which the result listener will be called.
- * @param callback The callback object to which the result will be returned.
- * If datagram is sent successfully, then
- * {@link OutcomeReceiver#onResult(Object)} will return datagramId.
- * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
- * will return a {@link SatelliteException} with the {@link SatelliteError}.
+ * @param resultListener Listener for the {@link SatelliteError} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void sendSatelliteDatagram(long datagramId, @DatagramType int datagramType,
+ public void sendSatelliteDatagram(@DatagramType int datagramType,
@NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
@NonNull @CallbackExecutor Executor executor,
- @NonNull OutcomeReceiver<Long, SatelliteException> callback) {
+ @SatelliteError @NonNull Consumer<Integer> resultListener) {
Objects.requireNonNull(datagram);
Objects.requireNonNull(executor);
- Objects.requireNonNull(callback);
+ Objects.requireNonNull(resultListener);
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- ResultReceiver receiver = new ResultReceiver(null) {
+ IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() {
@Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- if (resultCode == SATELLITE_ERROR_NONE) {
- if (resultData.containsKey(KEY_SEND_SATELLITE_DATAGRAM)) {
- long resultDatagramId = resultData
- .getLong(KEY_SEND_SATELLITE_DATAGRAM);
- executor.execute(() -> Binder.withCleanCallingIdentity(() ->
- callback.onResult(resultDatagramId)));
- } else {
- loge("KEY_SEND_SATELLITE_DATAGRAM does not exist.");
- executor.execute(() -> Binder.withCleanCallingIdentity(() ->
- callback.onError(
- new SatelliteException(SATELLITE_REQUEST_FAILED))));
- }
-
- } else {
- executor.execute(() -> Binder.withCleanCallingIdentity(() ->
- callback.onError(new SatelliteException(resultCode))));
- }
+ public void accept(int result) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(result)));
}
};
- telephony.sendSatelliteDatagram(mSubId, datagramId, datagramType, datagram,
- needFullScreenPointingUI, receiver);
+ telephony.sendSatelliteDatagram(mSubId, datagramType, datagram,
+ needFullScreenPointingUI, internalCallback);
} else {
throw new IllegalStateException("telephony service is null.");
}
diff --git a/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java
index e3e4171..d44a84d 100644
--- a/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java
@@ -93,7 +93,7 @@
/**@hide*/
@NonNull
- public final ISatellitePositionUpdateCallback getBinder() {
+ final ISatellitePositionUpdateCallback getBinder() {
return mBinder;
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
index cd084c9..2b6a5d9 100644
--- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
@@ -65,7 +65,7 @@
/**@hide*/
@NonNull
- public final ISatelliteProvisionStateCallback getBinder() {
+ final ISatelliteProvisionStateCallback getBinder() {
return mBinder;
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
index d24bee6..17d05b7 100644
--- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
@@ -84,7 +84,7 @@
/**@hide*/
@NonNull
- public final ISatelliteStateCallback getBinder() {
+ final ISatelliteStateCallback getBinder() {
return mBinder;
}
diff --git a/telephony/java/com/android/internal/telephony/ILongConsumer.aidl b/telephony/java/com/android/internal/telephony/ILongConsumer.aidl
new file mode 100644
index 0000000..2f0d4a0
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/ILongConsumer.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package com.android.internal.telephony;
+
+ /**
+ * Copies consumer pattern for an operation that requires long result from another process to
+ * finish.
+ */
+ oneway interface ILongConsumer {
+ void accept(long result);
+ }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index ef9dc3e..eb537bb 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2935,19 +2935,17 @@
* Send datagram over satellite.
*
* @param subId The subId of the subscription to send satellite datagrams for.
- * @param datagramId An id that uniquely identifies datagram requested to be sent.
* @param datagramType Type of datagram.
* @param datagram Datagram to send over satellite.
* @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
* full screen mode.
- * @param receiver Result receiver to get the datagramId if datagram is sent successfully else
- * error code of the request.
+ * @param callback The callback to get the error code of the request.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void sendSatelliteDatagram(int subId, long datagramId, int datagramType,
+ void sendSatelliteDatagram(int subId, int datagramType,
in SatelliteDatagram datagram, in boolean needFullScreenPointingUI,
- in ResultReceiver receiver);
+ IIntegerConsumer callback);
/**
* Request to get whether satellite communication is allowed for the current location.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
index 19ecf6a..05abf9f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
@@ -53,7 +53,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
+open class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
new file mode 100644
index 0000000..46899f3
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 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.tools.common.NavBar
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/** Some assertions will fail because of b/264415996 */
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppColdFromIconCfArm(flicker: FlickerTest) : OpenAppColdFromIcon(flicker) {
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ // TAPL fails on landscape mode b/240916028
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(NavBar.MODE_3BUTTON)
+ )
+ }
+ }
+}
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
index da98523..b848e63 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
@@ -93,8 +93,7 @@
.then()
// Animation starts, but the app may not be drawn yet which means the Splash
// may be visible.
- .isInvisible(testApp, isOptional = true)
- .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+ .isSplashScreenVisibleFor(testApp, isOptional = true)
.then()
// App shows up with the custom animation starting at alpha=1.
.isVisible(testApp)
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index 684b385d..6046415 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -434,14 +434,15 @@
/**
* Gets the list of hotspot networks the user can select to connect to.
*
- * @return Returns a {@link List} of {@link HotspotNetwork} objects, empty list on failure.
+ * @return Returns a {@link List} of {@link HotspotNetwork} objects, null on failure.
*/
@RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
android.Manifest.permission.NETWORK_SETUP_WIZARD})
- @NonNull
+ @SuppressWarnings("NullableCollection")
+ @Nullable
public List<HotspotNetwork> getHotspotNetworks() {
if (mService == null) {
- return List.of();
+ return null;
}
try {
@@ -449,20 +450,21 @@
} catch (RemoteException e) {
Log.e(TAG, "Exception in getHotspotNetworks", e);
}
- return List.of();
+ return null;
}
/**
* Gets the list of known networks the user can select to connect to.
*
- * @return Returns a {@link List} of {@link KnownNetwork} objects, empty list on failure.
+ * @return Returns a {@link List} of {@link KnownNetwork} objects, null on failure.
*/
@RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
android.Manifest.permission.NETWORK_SETUP_WIZARD})
- @NonNull
+ @SuppressWarnings("NullableCollection")
+ @Nullable
public List<KnownNetwork> getKnownNetworks() {
if (mService == null) {
- return List.of();
+ return null;
}
try {
@@ -470,7 +472,7 @@
} catch (RemoteException e) {
Log.e(TAG, "Exception in getKnownNetworks", e);
}
- return List.of();
+ return null;
}
/**
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
index 8c573e3..7578dfd 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
@@ -423,20 +423,20 @@
* Verify getters.
*/
@Test
- public void getHotspotNetworks_serviceNotConnected_shouldReturnEmptyList() {
+ public void getHotspotNetworks_serviceNotConnected_shouldReturnNull() {
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(null);
- assertThat(manager.getKnownNetworks()).isEmpty();
+ assertThat(manager.getHotspotNetworks()).isNull();
}
@Test
- public void getHotspotNetworks_remoteException_shouldReturnEmptyList() throws RemoteException {
+ public void getHotspotNetworks_remoteException_shouldReturnNull() throws RemoteException {
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(mService);
doThrow(new RemoteException()).when(mService).getHotspotNetworks();
- assertThat(manager.getKnownNetworks()).isEmpty();
+ assertThat(manager.getHotspotNetworks()).isNull();
}
@Test
@@ -450,21 +450,21 @@
}
@Test
- public void getKnownNetworks_serviceNotConnected_shouldReturnEmptyList()
+ public void getKnownNetworks_serviceNotConnected_shouldReturnNull()
throws RemoteException {
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(null);
- assertThat(manager.getKnownNetworks()).isEmpty();
+ assertThat(manager.getKnownNetworks()).isNull();
}
@Test
- public void getKnownNetworks_remoteException_shouldReturnEmptyList() throws RemoteException {
+ public void getKnownNetworks_remoteException_shouldReturnNull() throws RemoteException {
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(mService);
doThrow(new RemoteException()).when(mService).getKnownNetworks();
- assertThat(manager.getKnownNetworks()).isEmpty();
+ assertThat(manager.getKnownNetworks()).isNull();
}
@Test