diff options
154 files changed, 3102 insertions, 3264 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index e38e21febbf1..0650ce3d141c 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -1970,7 +1970,7 @@ public class DeviceIdleController extends SystemService } break; case MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR: { updatePreIdleFactor(); - maybeDoImmediateMaintenance(); + maybeDoImmediateMaintenance("idle factor"); } break; case MSG_REPORT_STATIONARY_STATUS: { final DeviceIdleInternal.StationaryListener newListener = @@ -3517,11 +3517,11 @@ public class DeviceIdleController extends SystemService // doze alarm to after the upcoming AlarmClock alarm. scheduleAlarmLocked( mAlarmManager.getNextWakeFromIdleTime() - mInjector.getElapsedRealtime() - + mConstants.QUICK_DOZE_DELAY_TIMEOUT, false); + + mConstants.QUICK_DOZE_DELAY_TIMEOUT); } else { // Wait a small amount of time in case something (eg: background service from // recently closed app) needs to finish running. - scheduleAlarmLocked(mConstants.QUICK_DOZE_DELAY_TIMEOUT, false); + scheduleAlarmLocked(mConstants.QUICK_DOZE_DELAY_TIMEOUT); } } else if (mState == STATE_ACTIVE) { moveToStateLocked(STATE_INACTIVE, "no activity"); @@ -3536,9 +3536,9 @@ public class DeviceIdleController extends SystemService // alarm to after the upcoming AlarmClock alarm. scheduleAlarmLocked( mAlarmManager.getNextWakeFromIdleTime() - mInjector.getElapsedRealtime() - + delay, false); + + delay); } else { - scheduleAlarmLocked(delay, false); + scheduleAlarmLocked(delay); } } } @@ -3753,7 +3753,7 @@ public class DeviceIdleController extends SystemService if (shouldUseIdleTimeoutFactorLocked()) { delay = (long) (mPreIdleFactor * delay); } - scheduleAlarmLocked(delay, false); + scheduleAlarmLocked(delay); moveToStateLocked(STATE_IDLE_PENDING, reason); break; case STATE_IDLE_PENDING: @@ -3779,7 +3779,7 @@ public class DeviceIdleController extends SystemService case STATE_SENSING: cancelSensingTimeoutAlarmLocked(); moveToStateLocked(STATE_LOCATING, reason); - scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT, false); + scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT); LocationManager locationManager = mInjector.getLocationManager(); if (locationManager != null && locationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) { @@ -3819,7 +3819,7 @@ public class DeviceIdleController extends SystemService // Everything is in place to go into IDLE state. case STATE_IDLE_MAINTENANCE: moveToStateLocked(STATE_IDLE, reason); - scheduleAlarmLocked(mNextIdleDelay, true); + scheduleAlarmLocked(mNextIdleDelay); if (DEBUG) Slog.d(TAG, "Moved to STATE_IDLE. Next alarm in " + mNextIdleDelay + " ms."); mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR); @@ -3842,7 +3842,7 @@ public class DeviceIdleController extends SystemService mActiveIdleOpCount = 1; mActiveIdleWakeLock.acquire(); moveToStateLocked(STATE_IDLE_MAINTENANCE, reason); - scheduleAlarmLocked(mNextIdlePendingDelay, false); + scheduleAlarmLocked(mNextIdlePendingDelay); if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE to STATE_IDLE_MAINTENANCE. " + "Next alarm in " + mNextIdlePendingDelay + " ms."); mMaintenanceStartTime = SystemClock.elapsedRealtime(); @@ -4013,19 +4013,18 @@ public class DeviceIdleController extends SystemService if (Math.abs(delay - newDelay) < MIN_STATE_STEP_ALARM_CHANGE) { return; } - scheduleAlarmLocked(newDelay, false); + scheduleAlarmLocked(newDelay); } } } - private void maybeDoImmediateMaintenance() { + private void maybeDoImmediateMaintenance(String reason) { synchronized (this) { if (mState == STATE_IDLE) { long duration = SystemClock.elapsedRealtime() - mIdleStartTime; - /* Let's trgger a immediate maintenance, - * if it has been idle for a long time */ + // Trigger an immediate maintenance window if it has been IDLE for long enough. if (duration > mConstants.IDLE_TIMEOUT) { - scheduleAlarmLocked(0, false); + stepIdleStateLocked(reason); } } } @@ -4045,7 +4044,7 @@ public class DeviceIdleController extends SystemService void setIdleStartTimeForTest(long idleStartTime) { synchronized (this) { mIdleStartTime = idleStartTime; - maybeDoImmediateMaintenance(); + maybeDoImmediateMaintenance("testing"); } } @@ -4224,8 +4223,9 @@ public class DeviceIdleController extends SystemService } @GuardedBy("this") - void scheduleAlarmLocked(long delay, boolean idleUntil) { - if (DEBUG) Slog.d(TAG, "scheduleAlarmLocked(" + delay + ", " + idleUntil + ")"); + @VisibleForTesting + void scheduleAlarmLocked(long delay) { + if (DEBUG) Slog.d(TAG, "scheduleAlarmLocked(" + delay + ", " + stateToString(mState) + ")"); if (mUseMotionSensor && mMotionSensor == null && mState != STATE_QUICK_DOZE_DELAY @@ -4241,7 +4241,7 @@ public class DeviceIdleController extends SystemService return; } mNextAlarmTime = SystemClock.elapsedRealtime() + delay; - if (idleUntil) { + if (mState == STATE_IDLE) { mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP, mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler); } else if (mState == STATE_LOCATING) { 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 b15fa9ee4fa4..056b6b913255 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -429,6 +429,7 @@ public class JobSchedulerService extends com.android.server.SystemService public void onPropertiesChanged(DeviceConfig.Properties properties) { boolean apiQuotaScheduleUpdated = false; boolean concurrencyUpdated = false; + boolean persistenceUpdated = false; boolean runtimeUpdated = false; for (int controller = 0; controller < mControllers.size(); controller++) { final StateController sc = mControllers.get(controller); @@ -488,9 +489,13 @@ public class JobSchedulerService extends com.android.server.SystemService runtimeUpdated = true; } break; + case Constants.KEY_MAX_NUM_PERSISTED_JOB_WORK_ITEMS: case Constants.KEY_PERSIST_IN_SPLIT_FILES: - mConstants.updatePersistingConstantsLocked(); - mJobs.setUseSplitFiles(mConstants.PERSIST_IN_SPLIT_FILES); + if (!persistenceUpdated) { + mConstants.updatePersistingConstantsLocked(); + mJobs.setUseSplitFiles(mConstants.PERSIST_IN_SPLIT_FILES); + persistenceUpdated = true; + } break; default: if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY) @@ -583,6 +588,9 @@ public class JobSchedulerService extends com.android.server.SystemService private static final String KEY_PERSIST_IN_SPLIT_FILES = "persist_in_split_files"; + private static final String KEY_MAX_NUM_PERSISTED_JOB_WORK_ITEMS = + "max_num_persisted_job_work_items"; + private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5; private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS; private static final float DEFAULT_HEAVY_USE_FACTOR = .9f; @@ -618,6 +626,7 @@ public class JobSchedulerService extends com.android.server.SystemService public static final long DEFAULT_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS = Math.min(Long.MAX_VALUE, DEFAULT_RUNTIME_UI_LIMIT_MS); static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = true; + static final int DEFAULT_MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 100_000; /** * Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early. @@ -761,6 +770,11 @@ public class JobSchedulerService extends com.android.server.SystemService public boolean PERSIST_IN_SPLIT_FILES = DEFAULT_PERSIST_IN_SPLIT_FILES; /** + * The maximum number of {@link JobWorkItem JobWorkItems} that can be persisted per job. + */ + public int MAX_NUM_PERSISTED_JOB_WORK_ITEMS = DEFAULT_MAX_NUM_PERSISTED_JOB_WORK_ITEMS; + + /** * If true, use TARE policy for job limiting. If false, use quotas. */ public boolean USE_TARE_POLICY = EconomyManager.DEFAULT_ENABLE_POLICY_JOB_SCHEDULER @@ -822,6 +836,10 @@ public class JobSchedulerService extends com.android.server.SystemService private void updatePersistingConstantsLocked() { PERSIST_IN_SPLIT_FILES = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_PERSIST_IN_SPLIT_FILES, DEFAULT_PERSIST_IN_SPLIT_FILES); + MAX_NUM_PERSISTED_JOB_WORK_ITEMS = DeviceConfig.getInt( + DeviceConfig.NAMESPACE_JOB_SCHEDULER, + KEY_MAX_NUM_PERSISTED_JOB_WORK_ITEMS, + DEFAULT_MAX_NUM_PERSISTED_JOB_WORK_ITEMS); } private void updatePrefetchConstantsLocked() { @@ -961,6 +979,8 @@ public class JobSchedulerService extends com.android.server.SystemService RUNTIME_UI_DATA_TRANSFER_LIMIT_MS).println(); pw.print(KEY_PERSIST_IN_SPLIT_FILES, PERSIST_IN_SPLIT_FILES).println(); + pw.print(KEY_MAX_NUM_PERSISTED_JOB_WORK_ITEMS, MAX_NUM_PERSISTED_JOB_WORK_ITEMS) + .println(); pw.print(Settings.Global.ENABLE_TARE, USE_TARE_POLICY).println(); @@ -1344,6 +1364,25 @@ public class JobSchedulerService extends com.android.server.SystemService // Fast path: we are adding work to an existing job, and the JobInfo is not // changing. We can just directly enqueue this work in to the job. if (toCancel.getJob().equals(job)) { + // On T and below, JobWorkItem count was unlimited but they could not be + // persisted. Now in U and above, we allow persisting them. In both cases, + // there is a danger of apps adding too many JobWorkItems and causing the + // system to OOM since we keep everything in memory. The persisting danger + // is greater because it could technically lead to a boot loop if the system + // keeps trying to load all the JobWorkItems that led to the initial OOM. + // Therefore, for now (partly for app compatibility), we tackle the latter + // and limit the number of JobWorkItems that can be persisted. + // Moving forward, we should look into two things: + // 1. Limiting the number of unpersisted JobWorkItems + // 2. Offloading some state to disk so we don't keep everything in memory + // TODO(273758274): improve JobScheduler's resilience and memory management + if (toCancel.getWorkCount() >= mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS + && toCancel.isPersisted()) { + Slog.w(TAG, "Too many JWIs for uid " + uId); + throw new IllegalStateException("Apps may not persist more than " + + mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS + + " JobWorkItems per job"); + } toCancel.enqueueWorkLocked(work); mJobs.touchJob(toCancel); @@ -1388,6 +1427,26 @@ public class JobSchedulerService extends com.android.server.SystemService jobStatus.prepareLocked(); if (toCancel != null) { + // On T and below, JobWorkItem count was unlimited but they could not be + // persisted. Now in U and above, we allow persisting them. In both cases, + // there is a danger of apps adding too many JobWorkItems and causing the + // system to OOM since we keep everything in memory. The persisting danger + // is greater because it could technically lead to a boot loop if the system + // keeps trying to load all the JobWorkItems that led to the initial OOM. + // Therefore, for now (partly for app compatibility), we tackle the latter + // and limit the number of JobWorkItems that can be persisted. + // Moving forward, we should look into two things: + // 1. Limiting the number of unpersisted JobWorkItems + // 2. Offloading some state to disk so we don't keep everything in memory + // TODO(273758274): improve JobScheduler's resilience and memory management + if (work != null && toCancel.isPersisted() + && toCancel.getWorkCount() >= mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS) { + Slog.w(TAG, "Too many JWIs for uid " + uId); + throw new IllegalStateException("Apps may not persist more than " + + mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS + + " JobWorkItems per job"); + } + // Implicitly replaces the existing job record with the new instance cancelJobImplLocked(toCancel, jobStatus, JobParameters.STOP_REASON_CANCELLED_BY_APP, JobParameters.INTERNAL_STOP_REASON_CANCELED, "job rescheduled by app"); @@ -1429,7 +1488,9 @@ public class JobSchedulerService extends com.android.server.SystemService /* isDeviceIdle */ false, /* hasConnectivityConstraintSatisfied */ false, /* hasContentTriggerConstraintSatisfied */ false, - 0); + 0, + jobStatus.getJob().isUserInitiated(), + /* isRunningAsUserInitiatedJob */ false); // If the job is immediately ready to run, then we can just immediately // put it in the pending list and try to schedule it. This is especially @@ -1848,7 +1909,9 @@ public class JobSchedulerService extends com.android.server.SystemService cancelled.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE), cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY), cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER), - 0); + 0, + cancelled.getJob().isUserInitiated(), + /* isRunningAsUserInitiatedJob */ false); } // If this is a replacement, bring in the new version of the job if (incomingJob != null) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index e60ed4ade9b7..4c339ac66160 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -462,7 +462,9 @@ public final class JobServiceContext implements ServiceConnection { job.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE), job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY), job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER), - mExecutionStartTimeElapsed - job.enqueueTime); + mExecutionStartTimeElapsed - job.enqueueTime, + job.getJob().isUserInitiated(), + job.shouldTreatAsUserInitiatedJob()); if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { // Use the context's ID to distinguish traces since there'll only be one job // running per context. @@ -1361,7 +1363,9 @@ public final class JobServiceContext implements ServiceConnection { completedJob.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE), completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY), completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER), - 0); + 0, + completedJob.getJob().isUserInitiated(), + completedJob.startedAsUserInitiatedJob); if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler", getId()); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 537a67039a82..0cc775870f88 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -814,6 +814,13 @@ public final class JobStatus { return null; } + /** Returns the number of {@link JobWorkItem JobWorkItems} attached to this job. */ + public int getWorkCount() { + final int pendingCount = pendingWork == null ? 0 : pendingWork.size(); + final int executingCount = executingWork == null ? 0 : executingWork.size(); + return pendingCount + executingCount; + } + public boolean hasWorkLocked() { return (pendingWork != null && pendingWork.size() > 0) || hasExecutingWorkLocked(); } diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java index 1ff389deedd2..dffed0f4a190 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java @@ -19,6 +19,7 @@ package com.android.server.tare; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.AppGlobals; import android.content.Context; import android.content.PermissionChecker; @@ -41,7 +42,8 @@ class InstalledPackageInfo { @Nullable public final String installerPackageName; - InstalledPackageInfo(@NonNull Context context, @NonNull PackageInfo packageInfo) { + InstalledPackageInfo(@NonNull Context context, @UserIdInt int userId, + @NonNull PackageInfo packageInfo) { final ApplicationInfo applicationInfo = packageInfo.applicationInfo; uid = applicationInfo == null ? NO_UID : applicationInfo.uid; packageName = packageInfo.packageName; @@ -55,7 +57,8 @@ class InstalledPackageInfo { applicationInfo.uid, packageName); InstallSourceInfo installSourceInfo = null; try { - installSourceInfo = AppGlobals.getPackageManager().getInstallSourceInfo(packageName); + installSourceInfo = AppGlobals.getPackageManager().getInstallSourceInfo(packageName, + userId); } catch (RemoteException e) { // Shouldn't happen. } diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java index caf72e828d69..ffb2c03fb02b 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java @@ -625,7 +625,8 @@ public class InternalResourceService extends SystemService { mPackageToUidCache.add(userId, pkgName, uid); } synchronized (mLock) { - final InstalledPackageInfo ipo = new InstalledPackageInfo(getContext(), packageInfo); + final InstalledPackageInfo ipo = new InstalledPackageInfo(getContext(), userId, + packageInfo); final InstalledPackageInfo oldIpo = mPkgCache.add(userId, pkgName, ipo); maybeUpdateInstallerStatusLocked(oldIpo, ipo); mUidToPackageCache.add(uid, pkgName); @@ -683,7 +684,7 @@ public class InternalResourceService extends SystemService { mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId); for (int i = pkgs.size() - 1; i >= 0; --i) { final InstalledPackageInfo ipo = - new InstalledPackageInfo(getContext(), pkgs.get(i)); + new InstalledPackageInfo(getContext(), userId, pkgs.get(i)); final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo); maybeUpdateInstallerStatusLocked(oldIpo, ipo); } @@ -963,7 +964,7 @@ public class InternalResourceService extends SystemService { mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId); for (int i = pkgs.size() - 1; i >= 0; --i) { final InstalledPackageInfo ipo = - new InstalledPackageInfo(getContext(), pkgs.get(i)); + new InstalledPackageInfo(getContext(), userId, pkgs.get(i)); final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo); maybeUpdateInstallerStatusLocked(oldIpo, ipo); } diff --git a/core/api/current.txt b/core/api/current.txt index 02de1cd9a137..79123c7f93ae 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8049,6 +8049,7 @@ package android.app.admin { field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED"; field public static final String ACTION_CHECK_POLICY_COMPLIANCE = "android.app.action.CHECK_POLICY_COMPLIANCE"; field public static final String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE"; + field public static final String ACTION_DEVICE_FINANCING_STATE_CHANGED = "android.app.admin.action.DEVICE_FINANCING_STATE_CHANGED"; field public static final String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED"; field public static final String ACTION_DEVICE_POLICY_RESOURCE_UPDATED = "android.app.action.DEVICE_POLICY_RESOURCE_UPDATED"; field public static final String ACTION_GET_PROVISIONING_MODE = "android.app.action.GET_PROVISIONING_MODE"; @@ -27478,7 +27479,7 @@ package android.media.tv { 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 onTvMessage(int, @NonNull android.os.Bundle); method public void onUnblockContent(android.media.tv.TvContentRating); method public void setOverlayViewEnabled(boolean); } @@ -53907,6 +53908,14 @@ package android.view { method public void removeViewImmediate(android.view.View); field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"; field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"; + field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION"; + field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH"; + field public static final String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE"; + field public static final String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE"; + field public static final String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE"; + field public static final String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS = "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS"; + field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS"; + field public static final String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION"; } public static class WindowManager.BadTokenException extends java.lang.RuntimeException { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 0352c07f1525..0a893f05e91c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -10134,16 +10134,18 @@ package android.net.wifi.sharedconnectivity.app { public final class SharedConnectivitySettingsState implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.os.Bundle getExtras(); + method @Nullable public android.app.PendingIntent getInstantTetherSettingsPendingIntent(); method public boolean isInstantTetherEnabled(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState> CREATOR; } public static final class SharedConnectivitySettingsState.Builder { - ctor public SharedConnectivitySettingsState.Builder(); + ctor public SharedConnectivitySettingsState.Builder(@NonNull android.content.Context); method @NonNull public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState build(); method @NonNull public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState.Builder setExtras(@NonNull android.os.Bundle); method @NonNull public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState.Builder setInstantTetherEnabled(boolean); + method @NonNull public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState.Builder setInstantTetherSettingsPendingIntent(@NonNull android.content.Intent); } } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 2751b54ebdb7..682fec8105d5 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -5840,26 +5840,6 @@ public final class ActivityThread extends ClientTransactionHandler r.activity.mChangingConfigurations = true; - // If we are preserving the main window across relaunches we would also like to preserve - // the children. However the client side view system does not support preserving - // the child views so we notify the window manager to expect these windows to - // be replaced and defer requests to destroy or hide them. This way we can achieve - // visual continuity. It's important that we do this here prior to pause and destroy - // as that is when we may hide or remove the child views. - // - // There is another scenario, if we have decided locally to relaunch the app from a - // call to recreate, then none of the windows will be prepared for replacement or - // preserved by the server, so we want to notify it that we are preparing to replace - // everything - try { - if (r.mPreserveWindow) { - WindowManagerGlobal.getWindowSession().prepareToReplaceWindows( - r.token, true /* childrenOnly */); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents, pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity"); } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 6301ad7f1278..999075d60e4c 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -2561,7 +2561,7 @@ public class ApplicationPackageManager extends PackageManager { public InstallSourceInfo getInstallSourceInfo(String packageName) throws NameNotFoundException { final InstallSourceInfo installSourceInfo; try { - installSourceInfo = mPM.getInstallSourceInfo(packageName); + installSourceInfo = mPM.getInstallSourceInfo(packageName, getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index e78fb179eb6c..0e89f57c8b54 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -4634,9 +4634,9 @@ public class Notification implements Parcelable * Set whether this is an "ongoing" notification. * * Ongoing notifications cannot be dismissed by the user on locked devices, or by - * notification listeners, and some notifications (device management, media) cannot be - * dismissed on unlocked devices, so your application or service must take - * care of canceling them. + * notification listeners, and some notifications (call, device management, media) cannot + * be dismissed on unlocked devices, so your application or service must take care of + * canceling them. * * They are typically used to indicate a background task that the user is actively engaged * with (e.g., playing music) or is pending in some way and therefore occupying the device diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index e72b14115b18..f7d2afba428e 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -54,6 +54,9 @@ per-file IBackupAgent.aidl = file:/services/backup/OWNERS per-file Broadcast* = file:/BROADCASTS_OWNERS per-file ReceiverInfo* = file:/BROADCASTS_OWNERS +# KeyguardManager +per-file KeyguardManager.java = file:/services/core/java/com/android/server/locksettings/OWNERS + # LocaleManager per-file *Locale* = file:/services/core/java/com/android/server/locales/OWNERS @@ -94,7 +97,5 @@ per-file Window* = file:/services/core/java/com/android/server/wm/OWNERS per-file ConfigurationController.java = file:/services/core/java/com/android/server/wm/OWNERS per-file *ScreenCapture* = file:/services/core/java/com/android/server/wm/OWNERS -# TODO(b/174932174): determine the ownership of KeyguardManager.java - # Zygote per-file *Zygote* = file:/ZYGOTE_OWNERS diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 024141777604..6bbbfe1ef4b0 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -57,6 +57,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa import android.Manifest.permission; import android.accounts.Account; +import android.annotation.BroadcastBehavior; import android.annotation.CallbackExecutor; import android.annotation.ColorInt; import android.annotation.IntDef; @@ -3998,6 +3999,27 @@ public class DevicePolicyManager { public static final String EXTRA_RESOURCE_IDS = "android.app.extra.RESOURCE_IDS"; + /** + * Broadcast Action: Broadcast sent to indicate that the device financing state has changed. + * + * <p>This occurs when, for example, a financing kiosk app has been added or removed. + * + * <p>To query the current device financing state see {@link #isDeviceFinanced}. + * + * <p>This will be delivered to the following apps if they include a receiver for this action + * in their manifest: + * <ul> + * <li>Device owner admins. + * <li>Organization-owned profile owner admins + * <li>The supervision app + * <li>The device management role holder + * </ul> + */ + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true, includeBackground = true) + public static final String ACTION_DEVICE_FINANCING_STATE_CHANGED = + "android.app.admin.action.DEVICE_FINANCING_STATE_CHANGED"; + /** Allow the user to choose whether to enable MTE on the device. */ public static final int MTE_NOT_CONTROLLED_BY_POLICY = 0; diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 132b9afe2ada..410994d61c2e 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -247,7 +247,7 @@ interface IPackageManager { @UnsupportedAppUsage String getInstallerPackageName(in String packageName); - InstallSourceInfo getInstallSourceInfo(in String packageName); + InstallSourceInfo getInstallSourceInfo(in String packageName, int userId); void resetApplicationPreferences(int userId); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 328b0ae96211..b9c671a8f3ea 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -8666,7 +8666,7 @@ public abstract class PackageManager { * requesting its own install information and is not an instant app. * * @param packageName The name of the package to query - * @throws NameNotFoundException if the given package name is not installed + * @throws NameNotFoundException if the given package name is not available to the caller. */ @NonNull public InstallSourceInfo getInstallSourceInfo(@NonNull String packageName) diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java index 33418006330c..5e523c0112b1 100644 --- a/core/java/android/database/sqlite/SQLiteOpenHelper.java +++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java @@ -513,6 +513,19 @@ public abstract class SQLiteOpenHelper implements AutoCloseable { * This method executes within a transaction. If an exception is thrown, all changes * will automatically be rolled back. * </p> + * <p> + * <em>Important:</em> You should NOT modify an existing migration step from version X to X+1 + * once a build has been released containing that migration step. If a migration step has an + * error and it runs on a device, the step will NOT re-run itself in the future if a fix is made + * to the migration step.</p> + * <p>For example, suppose a migration step renames a database column from {@code foo} to + * {@code bar} when the name should have been {@code baz}. If that migration step is released + * in a build and runs on a user's device, the column will be renamed to {@code bar}. If the + * developer subsequently edits this same migration step to change the name to {@code baz} as + * intended, the user devices which have already run this step will still have the name + * {@code bar}. Instead, a NEW migration step should be created to correct the error and rename + * {@code bar} to {@code baz}, ensuring the error is corrected on devices which have already run + * the migration step with the error.</p> * * @param db The database. * @param oldVersion The old database version. diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 8c4e90c81147..c92b1b8c120d 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -195,7 +195,7 @@ public final class Choreographer { private boolean mDebugPrintNextFrameTimeDelta; private int mFPSDivisor = 1; - private DisplayEventReceiver.VsyncEventData mLastVsyncEventData = + private final DisplayEventReceiver.VsyncEventData mLastVsyncEventData = new DisplayEventReceiver.VsyncEventData(); private final FrameData mFrameData = new FrameData(); @@ -857,7 +857,7 @@ public final class Choreographer { mFrameScheduled = false; mLastFrameTimeNanos = frameTimeNanos; mLastFrameIntervalNanos = frameIntervalNanos; - mLastVsyncEventData = vsyncEventData; + mLastVsyncEventData.copyFrom(vsyncEventData); } AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); @@ -1247,7 +1247,7 @@ public final class Choreographer { private boolean mHavePendingVsync; private long mTimestampNanos; private int mFrame; - private VsyncEventData mLastVsyncEventData = new VsyncEventData(); + private final VsyncEventData mLastVsyncEventData = new VsyncEventData(); FrameDisplayEventReceiver(Looper looper, int vsyncSource, long layerHandle) { super(looper, vsyncSource, /* eventRegistration */ 0, layerHandle); @@ -1287,7 +1287,7 @@ public final class Choreographer { mTimestampNanos = timestampNanos; mFrame = frame; - mLastVsyncEventData = vsyncEventData; + mLastVsyncEventData.copyFrom(vsyncEventData); Message msg = Message.obtain(mHandler, this); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java index b4675e0127de..54db34e788e9 100644 --- a/core/java/android/view/DisplayEventReceiver.java +++ b/core/java/android/view/DisplayEventReceiver.java @@ -146,7 +146,12 @@ public abstract class DisplayEventReceiver { mMessageQueue = null; } - static final class VsyncEventData { + /** + * Class to capture all inputs required for syncing events data. + * + * @hide + */ + public static final class VsyncEventData { // The amount of frame timeline choices. // Must be in sync with VsyncEventData::kFrameTimelinesLength in // frameworks/native/libs/gui/include/gui/VsyncEventData.h. If they do not match, a runtime @@ -164,6 +169,12 @@ public abstract class DisplayEventReceiver { this.deadline = deadline; } + void copyFrom(FrameTimeline other) { + vsyncId = other.vsyncId; + expectedPresentationTime = other.expectedPresentationTime; + deadline = other.deadline; + } + // The frame timeline vsync id, used to correlate a frame // produced by HWUI with the timeline data stored in Surface Flinger. public long vsyncId = FrameInfo.INVALID_VSYNC_ID; @@ -203,6 +214,14 @@ public abstract class DisplayEventReceiver { this.frameInterval = frameInterval; } + void copyFrom(VsyncEventData other) { + preferredFrameTimelineIndex = other.preferredFrameTimelineIndex; + frameInterval = other.frameInterval; + for (int i = 0; i < frameTimelines.length; i++) { + frameTimelines[i].copyFrom(other.frameTimelines[i]); + } + } + public FrameTimeline preferredFrameTimeline() { return frameTimelines[preferredFrameTimelineIndex]; } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 5810642402a3..48b112d0816d 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -110,16 +110,6 @@ interface IWindowSession { int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq, int lastSyncSeqId); - /* - * Notify the window manager that an application is relaunching and - * windows should be prepared for replacement. - * - * @param appToken The application - * @param childrenOnly Whether to only prepare child windows for replacement - * (for example when main windows are being reused via preservation). - */ - oneway void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly); - /** * Called by a client to report that it ran out of graphics memory. */ diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 35ed88fc420e..cc846e3537e1 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -863,10 +863,8 @@ public interface WindowManager extends ViewManager { * android:value="true|false"/> * </application> * </pre> - * - * @hide */ - // TODO(b/263984287): Make this public API. + // TODO(b/263984287): Add CTS tests. String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION"; @@ -899,8 +897,6 @@ public interface WindowManager extends ViewManager { * android:value="false"/> * </application> * </pre> - * - * @hide */ // TODO(b/263984287): Make this public API. String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS = @@ -937,10 +933,8 @@ public interface WindowManager extends ViewManager { * android:value="true|false"/> * </application> * </pre> - * - * @hide */ - // TODO(b/263984287): Make this public API. + // TODO(b/263984287): Add CTS tests. String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS"; /** @@ -976,10 +970,8 @@ public interface WindowManager extends ViewManager { * android:value="true|false"/> * </application> * </pre> - * - * @hide */ - // TODO(b/263984287): Make this public API. + // TODO(b/263984287): Add CTS tests. String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION"; @@ -1023,10 +1015,8 @@ public interface WindowManager extends ViewManager { * android:value="true|false"/> * </application> * </pre> - * - * @hide */ - // TODO(b/263984287): Make this public API. + // TODO(b/263984287): Add CTS tests. String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH"; @@ -1073,17 +1063,28 @@ public interface WindowManager extends ViewManager { * android:value="true|false"/> * </application> * </pre> - * - * @hide */ - // TODO(b/263984287): Make this public API. + // TODO(b/263984287): Add CTS tests. String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE"; /** * Application level {@link android.content.pm.PackageManager.Property PackageManager * .Property} for an app to inform the system that the app should be excluded from the - * compatibility override for orientation set by the device manufacturer. + * compatibility override for orientation set by the device manufacturer. When the orientation + * override is applied it can: + * <ul> + * <li>Replace the specific orientation requested by the app with another selected by the + device manufacturer, e.g. replace undefined requested by the app with portrait. + * <li>Always use an orientation selected by the device manufacturer. + * <li>Do one of the above but only when camera connection is open. + * </ul> + * + * <p>This property is different from {@link PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION} + * (which is used to avoid orientation loops caused by the incorrect use of {@link + * android.app.Activity#setRequestedOrientation}) because this property overrides the app to an + * orientation selected by the device manufacturer rather than ignoring one of orientation + * requests coming from the app while respecting the previous one. * * <p>With this property set to {@code true} or unset, device manufacturers can override * orientation for the app using their discretion to improve display compatibility. @@ -1099,10 +1100,8 @@ public interface WindowManager extends ViewManager { * android:value="true|false"/> * </application> * </pre> - * - * @hide */ - // TODO(b/263984287): Make this public API. + // TODO(b/263984287): Add CTS tests. String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE"; @@ -1144,10 +1143,8 @@ public interface WindowManager extends ViewManager { * android:value="true|false"/> * </application> * </pre> - * - * @hide */ - // TODO(b/263984287): Make this public API. + // TODO(b/263984287): Add CTS tests. String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE"; @@ -2815,7 +2812,7 @@ public interface WindowManager extends ViewManager { * * @hide */ - public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 0x00000002; + public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 1 << 1; /** * By default, wallpapers are sent new offsets when the wallpaper is scrolled. Wallpapers @@ -2826,7 +2823,7 @@ public interface WindowManager extends ViewManager { * * @hide */ - public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 0x00000004; + public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 1 << 2; /** * When set {@link LayoutParams#TYPE_APPLICATION_OVERLAY} windows will stay visible, even if @@ -2835,7 +2832,7 @@ public interface WindowManager extends ViewManager { * @hide */ @RequiresPermission(permission.SYSTEM_APPLICATION_OVERLAY) - public static final int PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY = 0x00000008; + public static final int PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY = 1 << 3; /** In a multiuser system if this flag is set and the owner is a system process then this * window will appear on all user screens. This overrides the default behavior of window @@ -2845,7 +2842,7 @@ public interface WindowManager extends ViewManager { * {@hide} */ @SystemApi @RequiresPermission(permission.INTERNAL_SYSTEM_WINDOW) - public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 0x00000010; + public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 1 << 4; /** * Flag to allow this window to have unrestricted gesture exclusion. @@ -2853,7 +2850,7 @@ public interface WindowManager extends ViewManager { * @see View#setSystemGestureExclusionRects(List) * @hide */ - public static final int PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION = 0x00000020; + public static final int PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION = 1 << 5; /** * Never animate position changes of the window. @@ -2862,20 +2859,20 @@ public interface WindowManager extends ViewManager { * {@hide} */ @UnsupportedAppUsage - public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 0x00000040; + public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 1 << 6; /** Window flag: special flag to limit the size of the window to be * original size ([320x480] x density). Used to create window for applications * running under compatibility mode. * * {@hide} */ - public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 0x00000080; + public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 1 << 7; /** Window flag: a special option intended for system dialogs. When * this flag is set, the window will demand focus unconditionally when * it is created. * {@hide} */ - public static final int PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100; + public static final int PRIVATE_FLAG_SYSTEM_ERROR = 1 << 8; /** * Flag to indicate that the view hierarchy of the window can only be measured when @@ -2884,14 +2881,14 @@ public interface WindowManager extends ViewManager { * views. This reduces the chances to perform measure. * {@hide} */ - public static final int PRIVATE_FLAG_OPTIMIZE_MEASURE = 0x00000200; + public static final int PRIVATE_FLAG_OPTIMIZE_MEASURE = 1 << 9; /** * Flag that prevents the wallpaper behind the current window from receiving touch events. * * {@hide} */ - public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 0x00000800; + public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 1 << 10; /** * Flag to force the status bar window to be visible all the time. If the bar is hidden when @@ -2900,7 +2897,7 @@ public interface WindowManager extends ViewManager { * * {@hide} */ - public static final int PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR = 0x00001000; + public static final int PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR = 1 << 11; /** * Flag to indicate that the window frame should be the requested frame adding the display @@ -2910,7 +2907,7 @@ public interface WindowManager extends ViewManager { * * {@hide} */ - public static final int PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT = 0x00002000; + public static final int PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT = 1 << 12; /** * Flag that will make window ignore app visibility and instead depend purely on the decor @@ -2918,39 +2915,28 @@ public interface WindowManager extends ViewManager { * drawing after it launches an app. * @hide */ - public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 0x00004000; - - /** - * Flag to indicate that this window is not expected to be replaced across - * configuration change triggered activity relaunches. In general the WindowManager - * expects Windows to be replaced after relaunch, and thus it will preserve their surfaces - * until the replacement is ready to show in order to prevent visual glitch. However - * some windows, such as PopupWindows expect to be cleared across configuration change, - * and thus should hint to the WindowManager that it should not wait for a replacement. - * @hide - */ - public static final int PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH = 0x00008000; + public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 1 << 13; /** * Flag to indicate that this child window should always be laid-out in the parent * frame regardless of the current windowing mode configuration. * @hide */ - public static final int PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME = 0x00010000; + public static final int PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME = 1 << 14; /** * Flag to indicate that this window is always drawing the status bar background, no matter * what the other flags are. * @hide */ - public static final int PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS = 0x00020000; + public static final int PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS = 1 << 15; /** * Flag to indicate that this window needs Sustained Performance Mode if * the device supports it. * @hide */ - public static final int PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE = 0x00040000; + public static final int PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE = 1 << 16; /** * Flag to indicate that any window added by an application process that is of type @@ -2961,7 +2947,7 @@ public interface WindowManager extends ViewManager { */ @SystemApi @RequiresPermission(permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS) - public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 0x00080000; + public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 1 << 19; /** * Indicates that this window is the rounded corners overlay present on some @@ -2969,7 +2955,7 @@ public interface WindowManager extends ViewManager { * screen magnification, and mirroring. * @hide */ - public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 0x00100000; + public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 1 << 20; /** * Flag to indicate that this window will be excluded while computing the magnifiable region @@ -2983,7 +2969,7 @@ public interface WindowManager extends ViewManager { * </p><p> * @hide */ - public static final int PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION = 0x00200000; + public static final int PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION = 1 << 21; /** * Flag to prevent the window from being magnified by the accessibility magnifier. @@ -2991,7 +2977,7 @@ public interface WindowManager extends ViewManager { * TODO(b/190623172): This is a temporary solution and need to find out another way instead. * @hide */ - public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 0x00400000; + public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 1 << 22; /** * Flag to indicate that the status bar window is in a state such that it forces showing @@ -3000,54 +2986,54 @@ public interface WindowManager extends ViewManager { * It only takes effects if this is set by {@link LayoutParams#TYPE_STATUS_BAR}. * @hide */ - public static final int PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION = 0x00800000; + public static final int PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION = 1 << 23; /** * Flag to indicate that the window is color space agnostic, and the color can be * interpreted to any color space. * @hide */ - public static final int PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC = 0x01000000; + public static final int PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC = 1 << 24; /** * Flag to request creation of a BLAST (Buffer as LayerState) Layer. * If not specified the client will receive a BufferQueue layer. * @hide */ - public static final int PRIVATE_FLAG_USE_BLAST = 0x02000000; + public static final int PRIVATE_FLAG_USE_BLAST = 1 << 25; /** * Flag to indicate that the window is controlling the appearance of system bars. So we * don't need to adjust it by reading its system UI flags for compatibility. * @hide */ - public static final int PRIVATE_FLAG_APPEARANCE_CONTROLLED = 0x04000000; + public static final int PRIVATE_FLAG_APPEARANCE_CONTROLLED = 1 << 26; /** * Flag to indicate that the window is controlling the behavior of system bars. So we don't * need to adjust it by reading its window flags or system UI flags for compatibility. * @hide */ - public static final int PRIVATE_FLAG_BEHAVIOR_CONTROLLED = 0x08000000; + public static final int PRIVATE_FLAG_BEHAVIOR_CONTROLLED = 1 << 27; /** * Flag to indicate that the window is controlling how it fits window insets on its own. * So we don't need to adjust its attributes for fitting window insets. * @hide */ - public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 0x10000000; + public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 1 << 28; /** * Flag to indicate that the window is a trusted overlay. * @hide */ - public static final int PRIVATE_FLAG_TRUSTED_OVERLAY = 0x20000000; + public static final int PRIVATE_FLAG_TRUSTED_OVERLAY = 1 << 29; /** * Flag to indicate that the parent frame of a window should be inset by IME. * @hide */ - public static final int PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME = 0x40000000; + public static final int PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME = 1 << 30; /** * Flag to indicate that we want to intercept and handle global drag and drop for all users. @@ -3062,7 +3048,7 @@ public interface WindowManager extends ViewManager { * @hide */ @RequiresPermission(permission.MANAGE_ACTIVITY_TASKS) - public static final int PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP = 0x80000000; + public static final int PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP = 1 << 31; /** * An internal annotation for flags that can be specified to {@link #softInputMode}. @@ -3093,7 +3079,6 @@ public interface WindowManager extends ViewManager { PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR, PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT, PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY, - PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH, PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME, PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS, PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE, @@ -3169,10 +3154,6 @@ public interface WindowManager extends ViewManager { equals = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY, name = "FORCE_DECOR_VIEW_VISIBILITY"), @ViewDebug.FlagToString( - mask = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH, - equals = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH, - name = "WILL_NOT_REPLACE_ON_RELAUNCH"), - @ViewDebug.FlagToString( mask = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME, equals = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME, name = "LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"), diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index b157ea0c641f..b77b7a698bd0 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -415,10 +415,6 @@ public class WindowlessWindowManager implements IWindowSession { } @Override - public void prepareToReplaceWindows(android.os.IBinder appToken, boolean childrenOnly) { - } - - @Override public boolean outOfMemory(android.view.IWindow window) { return false; } diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index c1ec168af145..d54addbbcb8d 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -19,7 +19,6 @@ package android.widget; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1644,8 +1643,7 @@ public class PopupWindow { p.width = mLastWidth = mWidth; } - p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH - | PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME; + p.privateFlags = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME; // Used for debugging. p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 076e4e118e66..1505ccce97a1 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -185,8 +185,13 @@ public class ZygoteInit { private static void preloadSharedLibraries() { Log.i(TAG, "Preloading shared libraries..."); System.loadLibrary("android"); - System.loadLibrary("compiler_rt"); System.loadLibrary("jnigraphics"); + + // TODO(b/206676167): This library is only used for renderscript today. When renderscript is + // removed, this load can be removed as well. + if (!SystemProperties.getBoolean("config.disable_renderscript", false)) { + System.loadLibrary("compiler_rt"); + } } native private static void nativePreloadAppProcessHALs(); diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp index 739055e040af..0c3ff6c28ea7 100644 --- a/core/jni/android_view_DisplayEventReceiver.cpp +++ b/core/jni/android_view_DisplayEventReceiver.cpp @@ -169,21 +169,25 @@ void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDispla gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval, vsyncEventData.frameInterval); - jobjectArray frameTimelinesObj = reinterpret_cast<jobjectArray>( - env->GetObjectField(vsyncEventDataObj.get(), - gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo - .frameTimelines)); + ScopedLocalRef<jobjectArray> + frameTimelinesObj(env, + reinterpret_cast<jobjectArray>( + env->GetObjectField(vsyncEventDataObj.get(), + gDisplayEventReceiverClassInfo + .vsyncEventDataClassInfo + .frameTimelines))); for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) { VsyncEventData::FrameTimeline& frameTimeline = vsyncEventData.frameTimelines[i]; - jobject frameTimelineObj = env->GetObjectArrayElement(frameTimelinesObj, i); - env->SetLongField(frameTimelineObj, + ScopedLocalRef<jobject> + frameTimelineObj(env, env->GetObjectArrayElement(frameTimelinesObj.get(), i)); + env->SetLongField(frameTimelineObj.get(), gDisplayEventReceiverClassInfo.frameTimelineClassInfo.vsyncId, frameTimeline.vsyncId); - env->SetLongField(frameTimelineObj, + env->SetLongField(frameTimelineObj.get(), gDisplayEventReceiverClassInfo.frameTimelineClassInfo .expectedPresentationTime, frameTimeline.expectedPresentationTime); - env->SetLongField(frameTimelineObj, + env->SetLongField(frameTimelineObj.get(), gDisplayEventReceiverClassInfo.frameTimelineClassInfo.deadline, frameTimeline.deadlineTimestamp); } diff --git a/core/res/res/layout/notification_expand_button.xml b/core/res/res/layout/notification_expand_button.xml index e752431ce75f..8eae064cba1f 100644 --- a/core/res/res/layout/notification_expand_button.xml +++ b/core/res/res/layout/notification_expand_button.xml @@ -19,23 +19,28 @@ android:id="@+id/expand_button" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:minHeight="@dimen/notification_header_height" android:layout_gravity="top|end" android:contentDescription="@string/expand_button_content_description_collapsed" - android:padding="16dp" + android:paddingHorizontal="16dp" > <LinearLayout android:id="@+id/expand_button_pill" android:layout_width="wrap_content" - android:layout_height="@dimen/notification_expand_button_pill_height" + android:layout_height="wrap_content" + android:minHeight="@dimen/notification_expand_button_pill_height" android:orientation="horizontal" android:background="@drawable/expand_button_pill_bg" + android:gravity="center_vertical" + android:layout_gravity="center_vertical" > <TextView android:id="@+id/expand_button_number" android:layout_width="wrap_content" - android:layout_height="@dimen/notification_expand_button_pill_height" + android:layout_height="wrap_content" + android:minHeight="@dimen/notification_expand_button_pill_height" android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info" android:gravity="center_vertical" android:paddingStart="8dp" diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml index fd787f6ea470..16a8bb7280a4 100644 --- a/core/res/res/layout/notification_template_material_base.xml +++ b/core/res/res/layout/notification_template_material_base.xml @@ -79,7 +79,8 @@ <NotificationTopLineView android:id="@+id/notification_top_line" android:layout_width="wrap_content" - android:layout_height="@dimen/notification_headerless_line_height" + android:layout_height="wrap_content" + android:minHeight="@dimen/notification_headerless_line_height" android:clipChildren="false" android:theme="@style/Theme.DeviceDefault.Notification" > diff --git a/core/res/res/layout/notification_template_material_call.xml b/core/res/res/layout/notification_template_material_call.xml index 1b3bd2673a7a..76bcc965f28b 100644 --- a/core/res/res/layout/notification_template_material_call.xml +++ b/core/res/res/layout/notification_template_material_call.xml @@ -29,7 +29,8 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="88dp" + android:layout_height="wrap_content" + android:minHeight="88dp" android:orientation="horizontal" > @@ -41,6 +42,7 @@ android:layout_marginStart="@dimen/conversation_content_start" android:orientation="vertical" android:minHeight="68dp" + android:paddingBottom="@dimen/notification_headerless_margin_twoline" > <include @@ -49,7 +51,10 @@ android:layout_height="wrap_content" /> - <include layout="@layout/notification_template_text" /> + <include layout="@layout/notification_template_text" + android:layout_height="wrap_content" + android:minHeight="@dimen/notification_text_height" + /> </LinearLayout> diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml index 95ddc2e4ea79..df32d30918c8 100644 --- a/core/res/res/layout/notification_template_material_media.xml +++ b/core/res/res/layout/notification_template_material_media.xml @@ -19,7 +19,8 @@ android:id="@+id/status_bar_latest_event_content" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="@dimen/notification_min_height" + android:layout_height="wrap_content" + android:minHeight="@dimen/notification_min_height" android:tag="media" > @@ -77,7 +78,8 @@ <NotificationTopLineView android:id="@+id/notification_top_line" android:layout_width="wrap_content" - android:layout_height="@dimen/notification_headerless_line_height" + android:layout_height="wrap_content" + android:minHeight="@dimen/notification_headerless_line_height" android:clipChildren="false" android:theme="@style/Theme.DeviceDefault.Notification" > diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml index bef1d0b319b4..3e82bd1814c6 100644 --- a/core/res/res/layout/notification_template_material_messaging.xml +++ b/core/res/res/layout/notification_template_material_messaging.xml @@ -102,7 +102,8 @@ <NotificationTopLineView android:id="@+id/notification_top_line" android:layout_width="wrap_content" - android:layout_height="@dimen/notification_headerless_line_height" + android:layout_height="wrap_content" + android:minHeight="@dimen/notification_headerless_line_height" android:layout_marginStart="@dimen/notification_content_margin_start" android:clipChildren="false" android:theme="@style/Theme.DeviceDefault.Notification" diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml index b35481d3c31b..97e753e2bdeb 100644 --- a/core/res/res/values/arrays.xml +++ b/core/res/res/values/arrays.xml @@ -227,6 +227,12 @@ <string-array name="device_state_notification_thermal_contents"> <item>@string/concurrent_display_notification_thermal_content</item> </string-array> + <string-array name="device_state_notification_power_save_titles"> + <item>@string/concurrent_display_notification_power_save_title</item> + </string-array> + <string-array name="device_state_notification_power_save_contents"> + <item>@string/concurrent_display_notification_power_save_content</item> + </string-array> <!-- Certificate digests for trusted apps that will be allowed to obtain the knownSigner of the demo device provisioning permissions. --> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 5bb86dc4b404..80bf7955c030 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -356,7 +356,7 @@ <dimen name="notification_headerless_margin_twoline">20dp</dimen> <!-- The height of each of the 1 or 2 lines in the headerless notification template --> - <dimen name="notification_headerless_line_height">24dp</dimen> + <dimen name="notification_headerless_line_height">24sp</dimen> <!-- vertical margin for the headerless notification content --> <dimen name="notification_headerless_min_height">56dp</dimen> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index d8e69d748324..6afdae508623 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6267,6 +6267,12 @@ ul.</string> <string name="concurrent_display_notification_thermal_title">Device is too warm</string> <!-- Content of concurrent display thermal notification. [CHAR LIMIT=NONE] --> <string name="concurrent_display_notification_thermal_content">Dual Screen is unavailable because your phone is getting too warm</string> + <!-- Title of concurrent display power saver notification. [CHAR LIMIT=NONE] --> + <string name="concurrent_display_notification_power_save_title">Dual Screen is unavailable</string> + <!-- Content of concurrent display power saver notification. [CHAR LIMIT=NONE] --> + <string name="concurrent_display_notification_power_save_content">Dual Screen is unavailable because Battery Saver is on. You can turn this off in Settings.</string> + <!-- Text of power saver notification settings button. [CHAR LIMIT=NONE] --> + <string name="device_state_notification_settings_button">Go to Settings</string> <!-- Text of device state notification turn off button. [CHAR LIMIT=NONE] --> <string name="device_state_notification_turn_off_button">Turn off</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c10612e16acf..1cb56e0c203b 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4931,12 +4931,17 @@ <java-symbol type="array" name="device_state_notification_active_contents"/> <java-symbol type="array" name="device_state_notification_thermal_titles"/> <java-symbol type="array" name="device_state_notification_thermal_contents"/> + <java-symbol type="array" name="device_state_notification_power_save_titles"/> + <java-symbol type="array" name="device_state_notification_power_save_contents"/> <java-symbol type="string" name="concurrent_display_notification_name"/> <java-symbol type="string" name="concurrent_display_notification_active_title"/> <java-symbol type="string" name="concurrent_display_notification_active_content"/> <java-symbol type="string" name="concurrent_display_notification_thermal_title"/> <java-symbol type="string" name="concurrent_display_notification_thermal_content"/> + <java-symbol type="string" name="concurrent_display_notification_power_save_title"/> + <java-symbol type="string" name="concurrent_display_notification_power_save_content"/> <java-symbol type="string" name="device_state_notification_turn_off_button"/> + <java-symbol type="string" name="device_state_notification_settings_button"/> <java-symbol type="bool" name="config_independentLockscreenLiveWallpaper"/> <java-symbol type="integer" name="config_deviceStateConcurrentRearDisplay" /> <java-symbol type="string" name="config_rearDisplayPhysicalAddress" /> diff --git a/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java new file mode 100644 index 000000000000..d7b911dda672 --- /dev/null +++ b/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java @@ -0,0 +1,59 @@ +/* + * 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.view.inputmethod; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import android.graphics.RectF; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.compatibility.common.util.ApiTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +@ApiTest(apis = {"android.view.inputmethod.DeleteRangeGesture.Builder#setGranularity", + "android.view.inputmethod.DeleteRangeGesture.Builder#setDeletionStartArea", + "android.view.inputmethod.DeleteRangeGesture.Builder#setDeletionEndArea", + "android.view.inputmethod.DeleteRangeGesture.Builder#setFallbackText", + "android.view.inputmethod.DeleteRangeGesture.Builder#build"}) +public class DeleteRangeGestureTest { + private static final RectF DELETION_START_RECTANGLE = new RectF(1, 2, 3, 4); + private static final RectF DELETION_END_RECTANGLE = new RectF(0, 2, 3, 4); + private static final String FALLBACK_TEXT = "fallback_test"; + + @Test + public void testBuilder() { + DeleteRangeGesture.Builder builder = new DeleteRangeGesture.Builder(); + DeleteRangeGesture gesture = builder.setGranularity(HandwritingGesture.GRANULARITY_WORD) + .setDeletionStartArea(DELETION_START_RECTANGLE) + .setDeletionEndArea(DELETION_END_RECTANGLE) + .setFallbackText(FALLBACK_TEXT).build(); + assertNotNull(gesture); + assertEquals(HandwritingGesture.GRANULARITY_WORD, gesture.getGranularity()); + assertEquals(DELETION_START_RECTANGLE, gesture.getDeletionStartArea()); + assertEquals(DELETION_END_RECTANGLE, gesture.getDeletionEndArea()); + assertEquals(FALLBACK_TEXT, gesture.getFallbackText()); + } +} diff --git a/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java new file mode 100644 index 000000000000..47a724d36038 --- /dev/null +++ b/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java @@ -0,0 +1,56 @@ +/* + * 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.view.inputmethod; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import android.graphics.PointF; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.compatibility.common.util.ApiTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +@ApiTest(apis = {"android.view.inputmethod.InsertGesture.Builder#setInsertionPoint", + "android.view.inputmethod.InsertGesture.Builder#setTextToInsert", + "android.view.inputmethod.InsertGesture.Builder#setFallbackText", + "android.view.inputmethod.InsertGesture.Builder#build"}) +public class InsertGestureTest { + private static final PointF INSERTION_POINT = new PointF(1, 2); + private static final String FALLBACK_TEXT = "fallback_text"; + private static final String TEXT_TO_INSERT = "text"; + + @Test + public void testBuilder() { + InsertGesture.Builder builder = new InsertGesture.Builder(); + InsertGesture gesture = builder.setInsertionPoint(INSERTION_POINT) + .setTextToInsert(TEXT_TO_INSERT) + .setFallbackText(FALLBACK_TEXT).build(); + assertNotNull(gesture); + assertEquals(INSERTION_POINT, gesture.getInsertionPoint()); + assertEquals(FALLBACK_TEXT, gesture.getFallbackText()); + assertEquals(TEXT_TO_INSERT, gesture.getTextToInsert()); + } +} diff --git a/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java new file mode 100644 index 000000000000..11ddba110f7a --- /dev/null +++ b/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java @@ -0,0 +1,57 @@ +/* + * 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.view.inputmethod; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import android.graphics.PointF; +import android.os.CancellationSignal; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.compatibility.common.util.ApiTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +@ApiTest(apis = {"android.view.inputmethod.InsertModeGesture.Builder#setInsertionPoint", + "android.view.inputmethod.InsertModeGesture.Builder#setCancellationSignal", + "android.view.inputmethod.InsertModeGesture.Builder#setFallbackText", + "android.view.inputmethod.InsertModeGesture.Builder#build"}) +public class InsertModeGestureTest { + private static final PointF INSERTION_POINT = new PointF(1, 2); + private static final String FALLBACK_TEXT = "fallback_text"; + private static final CancellationSignal CANCELLATION_SIGNAL = new CancellationSignal(); + + @Test + public void testBuilder() { + InsertModeGesture.Builder builder = new InsertModeGesture.Builder(); + InsertModeGesture gesture = builder.setInsertionPoint(INSERTION_POINT) + .setCancellationSignal(CANCELLATION_SIGNAL) + .setFallbackText(FALLBACK_TEXT).build(); + assertNotNull(gesture); + assertEquals(INSERTION_POINT, gesture.getInsertionPoint()); + assertEquals(FALLBACK_TEXT, gesture.getFallbackText()); + assertEquals(CANCELLATION_SIGNAL, gesture.getCancellationSignal()); + } +} diff --git a/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java new file mode 100644 index 000000000000..b2eb07c0a9e7 --- /dev/null +++ b/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java @@ -0,0 +1,55 @@ +/* + * 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.view.inputmethod; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import android.graphics.RectF; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.compatibility.common.util.ApiTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +@ApiTest(apis = {"android.view.inputmethod.SelectGesture.Builder#setGranularity", + "android.view.inputmethod.SelectGesture.Builder#setSelectionArea", + "android.view.inputmethod.SelectGesture.Builder#setFallbackText", + "android.view.inputmethod.SelectGesture.Builder#build"}) +public class SelectGestureTest { + private static final RectF SELECTION_RECTANGLE = new RectF(1, 2, 3, 4); + private static final String FALLBACK_TEXT = "fallback_text"; + + @Test + public void testBuilder() { + SelectGesture.Builder builder = new SelectGesture.Builder(); + SelectGesture gesture = builder.setGranularity(HandwritingGesture.GRANULARITY_WORD) + .setSelectionArea(SELECTION_RECTANGLE) + .setFallbackText(FALLBACK_TEXT).build(); + assertNotNull(gesture); + assertEquals(HandwritingGesture.GRANULARITY_WORD, gesture.getGranularity()); + assertEquals(SELECTION_RECTANGLE, gesture.getSelectionArea()); + assertEquals(FALLBACK_TEXT, gesture.getFallbackText()); + } +} diff --git a/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java new file mode 100644 index 000000000000..df63a4aaaefe --- /dev/null +++ b/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java @@ -0,0 +1,59 @@ +/* + * 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.view.inputmethod; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import android.graphics.RectF; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.compatibility.common.util.ApiTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +@ApiTest(apis = {"android.view.inputmethod.SelectRangeGesture.Builder#setGranularity", + "android.view.inputmethod.SelectRangeGesture.Builder#setSelectionStartArea", + "android.view.inputmethod.SelectRangeGesture.Builder#setSelectionEndArea", + "android.view.inputmethod.SelectRangeGesture.Builder#setFallbackText", + "android.view.inputmethod.SelectRangeGesture.Builder#build"}) +public class SelectRangeGestureTest { + private static final RectF SELECTION_START_RECTANGLE = new RectF(1, 2, 3, 4); + private static final RectF SELECTION_END_RECTANGLE = new RectF(0, 2, 3, 4); + private static final String FALLBACK_TEXT = "fallback_text"; + + @Test + public void testBuilder() { + SelectRangeGesture.Builder builder = new SelectRangeGesture.Builder(); + SelectRangeGesture gesture = builder.setGranularity(HandwritingGesture.GRANULARITY_WORD) + .setSelectionStartArea(SELECTION_START_RECTANGLE) + .setSelectionEndArea(SELECTION_END_RECTANGLE) + .setFallbackText(FALLBACK_TEXT).build(); + assertNotNull(gesture); + assertEquals(HandwritingGesture.GRANULARITY_WORD, gesture.getGranularity()); + assertEquals(SELECTION_START_RECTANGLE, gesture.getSelectionStartArea()); + assertEquals(SELECTION_END_RECTANGLE, gesture.getSelectionEndArea()); + assertEquals(FALLBACK_TEXT, gesture.getFallbackText()); + } +} diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index ffc5ff226607..5549f88b65e0 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -259,12 +259,6 @@ "group": "WM_DEBUG_BOOT", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "-1878839956": { - "message": "Marking app token %s with replacing windows.", - "level": "DEBUG", - "group": "WM_DEBUG_ADD_REMOVE", - "at": "com\/android\/server\/wm\/ActivityRecord.java" - }, "-1872288685": { "message": "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b Callers=%s", "level": "VERBOSE", @@ -463,12 +457,6 @@ "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/Task.java" }, - "-1698815688": { - "message": "Resetting app token %s of replacing window marks.", - "level": "DEBUG", - "group": "WM_DEBUG_ADD_REMOVE", - "at": "com\/android\/server\/wm\/ActivityRecord.java" - }, "-1679411993": { "message": "setVr2dDisplayId called for: %d", "level": "DEBUG", @@ -481,12 +469,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "-1661704580": { - "message": "Attempted to set replacing window on non-existing app token %s", - "level": "WARN", - "group": "WM_ERROR", - "at": "com\/android\/server\/wm\/WindowManagerService.java" - }, "-1647332198": { "message": "remove RecentTask %s when finishing user %d", "level": "INFO", @@ -613,12 +595,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "-1515151503": { - "message": ">>> OPEN TRANSACTION removeReplacedWindows", - "level": "INFO", - "group": "WM_SHOW_TRANSACTIONS", - "at": "com\/android\/server\/wm\/RootWindowContainer.java" - }, "-1501564055": { "message": "Organized TaskFragment is not ready= %s", "level": "VERBOSE", @@ -691,12 +667,6 @@ "group": "WM_DEBUG_TASKS", "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java" }, - "-1471946192": { - "message": "Marking app token %s with replacing child windows.", - "level": "DEBUG", - "group": "WM_DEBUG_ADD_REMOVE", - "at": "com\/android\/server\/wm\/ActivityRecord.java" - }, "-1471518109": { "message": "Set animatingExit: reason=onAppVisibilityChanged win=%s", "level": "DEBUG", @@ -919,12 +889,6 @@ "group": "WM_DEBUG_BACK_PREVIEW", "at": "com\/android\/server\/wm\/BackNavigationController.java" }, - "-1270731689": { - "message": "Attempted to set replacing window on app token with no content %s", - "level": "WARN", - "group": "WM_ERROR", - "at": "com\/android\/server\/wm\/WindowManagerService.java" - }, "-1263316010": { "message": "Computed rotation=%s (%d) for display id=%d based on lastOrientation=%s (%d) and oldRotation=%s (%d)", "level": "VERBOSE", @@ -1417,12 +1381,6 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java" }, - "-799003045": { - "message": "Set animatingExit: reason=remove\/replaceWindow win=%s", - "level": "VERBOSE", - "group": "WM_DEBUG_ANIM", - "at": "com\/android\/server\/wm\/WindowState.java" - }, "-787664727": { "message": "Cannot launch dream activity due to invalid state. dream component: %s packageName: %s", "level": "ERROR", @@ -1453,6 +1411,12 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/Transition.java" }, + "-778347463": { + "message": "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b mDisplayFrozen=%b callers=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "-775004869": { "message": "Not a match: %s", "level": "DEBUG", @@ -1963,12 +1927,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "-320419645": { - "message": "Removing replaced window: %s", - "level": "DEBUG", - "group": "WM_DEBUG_ADD_REMOVE", - "at": "com\/android\/server\/wm\/WindowState.java" - }, "-319689203": { "message": "Reparenting to original parent: %s for %s", "level": "INFO", @@ -2335,12 +2293,6 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, - "38267433": { - "message": "Attempted to reset replacing window on non-existing app token %s", - "level": "WARN", - "group": "WM_ERROR", - "at": "com\/android\/server\/wm\/WindowManagerService.java" - }, "45285419": { "message": "startingWindow was set but startingSurface==null, couldn't remove", "level": "VERBOSE", @@ -2989,12 +2941,6 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, - "594260654": { - "message": "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b mWillReplaceWindow=%b mDisplayFrozen=%b callers=%s", - "level": "VERBOSE", - "group": "WM_DEBUG_APP_TRANSITIONS", - "at": "com\/android\/server\/wm\/WindowState.java" - }, "600140673": { "message": "checkBootAnimationComplete: Waiting for anim complete", "level": "INFO", @@ -3829,12 +3775,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "1423592961": { - "message": "<<< CLOSE TRANSACTION removeReplacedWindows", - "level": "INFO", - "group": "WM_SHOW_TRANSACTIONS", - "at": "com\/android\/server\/wm\/RootWindowContainer.java" - }, "1430336882": { "message": "findFocusedWindow: focusedApp windows not focusable using new focus @ %s", "level": "VERBOSE", @@ -3901,12 +3841,6 @@ "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/WindowState.java" }, - "1515161239": { - "message": "removeDeadWindows: %s", - "level": "WARN", - "group": "WM_DEBUG_ADD_REMOVE", - "at": "com\/android\/server\/wm\/ActivityRecord.java" - }, "1518495446": { "message": "removeWindowToken: Attempted to remove non-existing token: %s", "level": "WARN", @@ -4273,12 +4207,6 @@ "group": "WM_DEBUG_WINDOW_ORGANIZER", "at": "com\/android\/server\/wm\/TaskOrganizerController.java" }, - "1921821199": { - "message": "Preserving %s until the new one is added", - "level": "VERBOSE", - "group": "WM_DEBUG_ADD_REMOVE", - "at": "com\/android\/server\/wm\/WindowState.java" - }, "1928325128": { "message": "Run showImeRunner", "level": "DEBUG", @@ -4489,12 +4417,6 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/AppTransitionController.java" }, - "2114149926": { - "message": "Not removing %s because app died while it's visible", - "level": "VERBOSE", - "group": "WM_DEBUG_ADD_REMOVE", - "at": "com\/android\/server\/wm\/WindowState.java" - }, "2117696413": { "message": "moveTaskToFront: moving taskId=%d", "level": "DEBUG", diff --git a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml index 298ad3025b00..8d1da0f7ad1b 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml @@ -63,11 +63,11 @@ android:tint="@color/bubbles_icon_tint"/> <TextView + android:id="@+id/bubble_manage_menu_dont_bubble_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault" - android:text="@string/bubbles_dont_bubble_conversation" /> + android:textAppearance="@*android:style/TextAppearance.DeviceDefault" /> </LinearLayout> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index 3082962e1a8b..9f6cf79dcbc4 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -146,6 +146,8 @@ <string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string> <!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=30] --> <string name="bubble_dismiss_text">Dismiss bubble</string> + <!-- Button text to stop an app from bubbling [CHAR LIMIT=60]--> + <string name="bubbles_dont_bubble">Don\u2019t bubble</string> <!-- Button text to stop a conversation from bubbling [CHAR LIMIT=60]--> <string name="bubbles_dont_bubble_conversation">Don\u2019t bubble conversation</string> <!-- Title text for the bubbles feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=60]--> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 0b947c8b9b08..deb4fd5f19bb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -844,6 +844,8 @@ public class BubbleStackView extends FrameLayout private DismissView mDismissView; private ViewGroup mManageMenu; + private TextView mManageDontBubbleText; + private ViewGroup mManageSettingsView; private ImageView mManageSettingsIcon; private TextView mManageSettingsText; private boolean mShowingManage = false; @@ -1217,7 +1219,11 @@ public class BubbleStackView extends FrameLayout mUnbubbleConversationCallback.accept(mBubbleData.getSelectedBubble().getKey()); }); - mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener( + mManageDontBubbleText = mManageMenu + .findViewById(R.id.bubble_manage_menu_dont_bubble_text); + + mManageSettingsView = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container); + mManageSettingsView.setOnClickListener( view -> { showManageMenu(false /* show */); final BubbleViewProvider bubble = mBubbleData.getSelectedBubble(); @@ -2868,10 +2874,19 @@ public class BubbleStackView extends FrameLayout // name and icon. if (show) { final Bubble bubble = mBubbleData.getBubbleInStackWithKey(mExpandedBubble.getKey()); - if (bubble != null) { + if (bubble != null && !bubble.isAppBubble()) { + // Setup options for non app bubbles + mManageDontBubbleText.setText(R.string.bubbles_dont_bubble_conversation); mManageSettingsIcon.setImageBitmap(bubble.getRawAppBadge()); mManageSettingsText.setText(getResources().getString( R.string.bubbles_app_settings, bubble.getAppName())); + mManageSettingsView.setVisibility(VISIBLE); + } else { + // Setup options for app bubbles + mManageDontBubbleText.setText(R.string.bubbles_dont_bubble); + // App bubbles are not notification based + // so we don't show the option to go to notification settings + mManageSettingsView.setVisibility(GONE); } } @@ -2936,6 +2951,15 @@ public class BubbleStackView extends FrameLayout } } + /** + * Checks whether manage menu notification settings action is available and visible + * Used for testing + */ + @VisibleForTesting + public boolean isManageMenuSettingsVisible() { + return mManageSettingsView != null && mManageSettingsView.getVisibility() == VISIBLE; + } + private void updateExpandedBubble() { if (DEBUG_BUBBLE_STACK_VIEW) { Log.d(TAG, "updateExpandedBubble()"); 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 a5546e554422..a1eaf851da23 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 @@ -259,37 +259,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } }; - private final SplitScreenTransitions.TransitionFinishedCallback - mRecentTransitionFinishedCallback = - new SplitScreenTransitions.TransitionFinishedCallback() { - @Override - public void onFinished(WindowContainerTransaction finishWct, - SurfaceControl.Transaction finishT) { - // Check if the recent transition is finished by returning to the current - // split, so we - // can restore the divider bar. - for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) { - final WindowContainerTransaction.HierarchyOp op = - finishWct.getHierarchyOps().get(i); - final IBinder container = op.getContainer(); - if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop() - && (mMainStage.containsContainer(container) - || mSideStage.containsContainer(container))) { - updateSurfaceBounds(mSplitLayout, finishT, - false /* applyResizingOffset */); - setDividerVisibility(true, finishT); - return; - } - } - - // Dismiss the split screen if it's not returning to split. - prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct); - setSplitsVisible(false); - setDividerVisibility(false, finishT); - logExit(EXIT_REASON_UNKNOWN); - } - }; - protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, ShellTaskOrganizer taskOrganizer, DisplayController displayController, DisplayImeController displayImeController, @@ -388,6 +357,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return mMainStage.isActive(); } + /** Checks if `transition` is a pending enter-split transition. */ + public boolean isPendingEnter(IBinder transition) { + return mSplitTransitions.isPendingEnter(transition); + } + @StageType int getStageOfTask(int taskId) { if (mMainStage.containsTask(taskId)) { @@ -2264,11 +2238,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } else if (isOpening && inFullscreen) { final int activityType = triggerTask.getActivityType(); - if (activityType == ACTIVITY_TYPE_HOME - || activityType == ACTIVITY_TYPE_RECENTS) { - // Enter overview panel, so start recent transition. - mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(), - mRecentTransitionFinishedCallback); + if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { + if (request.getRemoteTransition() != null) { + // starting recents/home, so don't handle this and let it fall-through to + // the remote handler. + return null; + } + // Need to use the old stuff for non-remote animations, otherwise we don't + // exit split-screen. + mSplitTransitions.setRecentTransition(transition, null /* remote */, + this::onRecentsInSplitAnimationFinish); } } } else { @@ -2398,7 +2377,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, shouldAnimate = startPendingEnterAnimation( transition, info, startTransaction, finishTransaction); } else if (mSplitTransitions.isPendingRecent(transition)) { - shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction); + onRecentsInSplitAnimationStart(startTransaction); } else if (mSplitTransitions.isPendingDismiss(transition)) { shouldAnimate = startPendingDismissAnimation( mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction); @@ -2653,10 +2632,35 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } - private boolean startPendingRecentAnimation(@NonNull IBinder transition, - @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { + /** Call this when starting the open-recents animation while split-screen is active. */ + public void onRecentsInSplitAnimationStart(@NonNull SurfaceControl.Transaction t) { setDividerVisibility(false, t); - return true; + } + + /** Call this when the recents animation during split-screen finishes. */ + public void onRecentsInSplitAnimationFinish(WindowContainerTransaction finishWct, + SurfaceControl.Transaction finishT) { + // Check if the recent transition is finished by returning to the current + // split, so we can restore the divider bar. + for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) { + final WindowContainerTransaction.HierarchyOp op = + finishWct.getHierarchyOps().get(i); + final IBinder container = op.getContainer(); + if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop() + && (mMainStage.containsContainer(container) + || mSideStage.containsContainer(container))) { + updateSurfaceBounds(mSplitLayout, finishT, + false /* applyResizingOffset */); + setDividerVisibility(true, finishT); + return; + } + } + + // Dismiss the split screen if it's not returning to split. + prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct); + setSplitsVisible(false); + setDividerVisibility(false, finishT); + logExit(EXIT_REASON_UNKNOWN); } private void addDividerBarToTransition(@NonNull TransitionInfo info, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 2e864483bf1d..d0948923dc6e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -18,6 +18,7 @@ package com.android.wm.shell.transition; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; @@ -25,6 +26,7 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; +import static com.android.wm.shell.util.TransitionUtil.isOpeningType; import android.annotation.NonNull; import android.annotation.Nullable; @@ -68,14 +70,20 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { /** Pip was entered while handling an intent with its own remoteTransition. */ static final int TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE = 3; + /** Recents transition while split-screen active. */ + static final int TYPE_RECENTS_DURING_SPLIT = 4; + /** The default animation for this mixed transition. */ static final int ANIM_TYPE_DEFAULT = 0; /** For ENTER_PIP_FROM_SPLIT, indicates that this is a to-home animation. */ static final int ANIM_TYPE_GOING_HOME = 1; + /** For RECENTS_DURING_SPLIT, is set when this turns into a pair->pair task switch. */ + static final int ANIM_TYPE_PAIR_TO_PAIR = 1; + final int mType; - int mAnimType = 0; + int mAnimType = ANIM_TYPE_DEFAULT; final IBinder mTransition; Transitions.TransitionHandler mLeftoversHandler = null; @@ -167,6 +175,27 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { mixed.mLeftoversHandler = handler.first; mActiveTransitions.add(mixed); return handler.second; + } else if (mSplitHandler.isSplitActive() + && isOpeningType(request.getType()) + && request.getTriggerTask() != null + && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FULLSCREEN + && (request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_HOME + || request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_RECENTS) + && request.getRemoteTransition() != null) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " + + "Split-Screen is active, so treat it as Mixed."); + Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler = + mPlayer.dispatchRequest(transition, request, this); + if (handler == null) { + android.util.Log.e(Transitions.TAG, " No handler for remote? This is unexpected" + + ", there should at-least be RemoteHandler."); + return null; + } + final MixedTransition mixed = new MixedTransition( + MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition); + mixed.mLeftoversHandler = handler.first; + mActiveTransitions.add(mixed); + return handler.second; } return null; } @@ -216,6 +245,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { return animateOpenIntentWithRemoteAndPip(mixed, info, startTransaction, finishTransaction, finishCallback); + } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { + return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction, + finishCallback); } else { mActiveTransitions.remove(mixed); throw new IllegalStateException("Starting mixed animation without a known mixed type? " @@ -441,12 +473,40 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { return true; } + private boolean animateRecentsDuringSplit(@NonNull final MixedTransition mixed, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + // Split-screen is only interested in the recents transition finishing (and merging), so + // just wrap finish and start recents animation directly. + Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + mixed.mInFlightSubAnimations = 0; + mActiveTransitions.remove(mixed); + // If pair-to-pair switching, the post-recents clean-up isn't needed. + if (mixed.mAnimType != MixedTransition.ANIM_TYPE_PAIR_TO_PAIR) { + wct = wct != null ? wct : new WindowContainerTransaction(); + mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction); + } + mSplitHandler.onTransitionAnimationComplete(); + finishCallback.onTransitionFinished(wct, wctCB); + }; + mixed.mInFlightSubAnimations = 1; + mSplitHandler.onRecentsInSplitAnimationStart(startTransaction); + final boolean handled = mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info, + startTransaction, finishTransaction, finishCB); + if (!handled) { + mActiveTransitions.remove(mixed); + } + return handled; + } + @Override public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback) { for (int i = 0; i < mActiveTransitions.size(); ++i) { - if (mActiveTransitions.get(i) != mergeTarget) continue; + if (mActiveTransitions.get(i).mTransition != mergeTarget) continue; MixedTransition mixed = mActiveTransitions.get(i); if (mixed.mInFlightSubAnimations <= 0) { // Already done, so no need to end it. @@ -474,6 +534,14 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); } + } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { + if (mSplitHandler.isPendingEnter(transition)) { + // Recents -> enter-split means that we are switching from one pair to + // another pair. + mixed.mAnimType = MixedTransition.ANIM_TYPE_PAIR_TO_PAIR; + } + mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, + finishCallback); } else { throw new IllegalStateException("Playing a mixed transition with unknown type? " + mixed.mType); @@ -493,6 +561,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { if (mixed == null) return; if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { mPipHandler.onTransitionConsumed(transition, aborted, finishT); + } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { + mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index 3901dabcaec8..df78d92a90c8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -35,6 +35,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_P import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -249,7 +250,7 @@ public class SplitTransitionTests extends ShellTestCase { @Test @UiThreadTest - public void testEnterRecents() { + public void testEnterRecentsAndCommit() { enterSplit(); ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder() @@ -258,27 +259,65 @@ public class SplitTransitionTests extends ShellTestCase { .build(); // Create a request to bring home forward - TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, null); + TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, + mock(RemoteTransition.class)); IBinder transition = mock(IBinder.class); WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request); - - assertTrue(result.isEmpty()); + // Don't handle recents opening + assertNull(result); // make sure we haven't made any local changes yet (need to wait until transition is ready) assertTrue(mStageCoordinator.isSplitScreenVisible()); - // simulate the transition - TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TO_FRONT, 0) - .addChange(TRANSIT_TO_FRONT, homeTask) - .addChange(TRANSIT_TO_BACK, mMainChild) - .addChange(TRANSIT_TO_BACK, mSideChild) + // simulate the start of recents transition + mMainStage.onTaskVanished(mMainChild); + mSideStage.onTaskVanished(mSideChild); + mStageCoordinator.onRecentsInSplitAnimationStart(mock(SurfaceControl.Transaction.class)); + assertTrue(mStageCoordinator.isSplitScreenVisible()); + + // Make sure it cleans-up if recents doesn't restore + WindowContainerTransaction commitWCT = new WindowContainerTransaction(); + mStageCoordinator.onRecentsInSplitAnimationFinish(commitWCT, + mock(SurfaceControl.Transaction.class)); + assertFalse(mStageCoordinator.isSplitScreenVisible()); + } + + @Test + @UiThreadTest + public void testEnterRecentsAndRestore() { + enterSplit(); + + ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder() + .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .setActivityType(ACTIVITY_TYPE_HOME) .build(); + + // Create a request to bring home forward + TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, + mock(RemoteTransition.class)); + IBinder transition = mock(IBinder.class); + WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request); + // Don't handle recents opening + assertNull(result); + + // make sure we haven't made any local changes yet (need to wait until transition is ready) + assertTrue(mStageCoordinator.isSplitScreenVisible()); + + // simulate the start of recents transition mMainStage.onTaskVanished(mMainChild); mSideStage.onTaskVanished(mSideChild); - mStageCoordinator.startAnimation(transition, info, - mock(SurfaceControl.Transaction.class), - mock(SurfaceControl.Transaction.class), - mock(Transitions.TransitionFinishCallback.class)); + mStageCoordinator.onRecentsInSplitAnimationStart(mock(SurfaceControl.Transaction.class)); + assertTrue(mStageCoordinator.isSplitScreenVisible()); + + // Make sure we remain in split after recents restores. + WindowContainerTransaction restoreWCT = new WindowContainerTransaction(); + restoreWCT.reorder(mMainChild.token, true /* toTop */); + restoreWCT.reorder(mSideChild.token, true /* toTop */); + // simulate the restoreWCT being applied: + mMainStage.onTaskAppeared(mMainChild, mock(SurfaceControl.class)); + mSideStage.onTaskAppeared(mSideChild, mock(SurfaceControl.class)); + mStageCoordinator.onRecentsInSplitAnimationFinish(restoreWCT, + mock(SurfaceControl.Transaction.class)); assertTrue(mStageCoordinator.isSplitScreenVisible()); } diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index b0896daee2a1..9df6822b4867 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -91,6 +91,8 @@ bool Properties::isHighEndGfx = true; bool Properties::isLowRam = false; bool Properties::isSystemOrPersistent = false; +float Properties::maxHdrHeadroomOn8bit = 5.f; // TODO: Refine this number + StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI; DrawingEnabled Properties::drawingEnabled = DrawingEnabled::NotInitialized; @@ -150,6 +152,11 @@ bool Properties::load() { enableWebViewOverlays = base::GetBoolProperty(PROPERTY_WEBVIEW_OVERLAYS_ENABLED, true); + auto hdrHeadroom = (float)atof(base::GetProperty(PROPERTY_8BIT_HDR_HEADROOM, "").c_str()); + if (hdrHeadroom >= 1.f) { + maxHdrHeadroomOn8bit = std::min(hdrHeadroom, 100.f); + } + // call isDrawingEnabled to force loading of the property isDrawingEnabled(); diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index ed7175e140e4..24e206bbc3b1 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -218,6 +218,8 @@ enum DebugLevel { #define PROPERTY_MEMORY_POLICY "debug.hwui.app_memory_policy" +#define PROPERTY_8BIT_HDR_HEADROOM "debug.hwui.8bit_hdr_headroom" + /////////////////////////////////////////////////////////////////////////////// // Misc /////////////////////////////////////////////////////////////////////////////// @@ -321,6 +323,8 @@ public: static bool isLowRam; static bool isSystemOrPersistent; + static float maxHdrHeadroomOn8bit; + static StretchEffectBehavior getStretchEffectBehavior() { return stretchEffectBehavior; } diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index f10b2b2f0694..dd781bb85470 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -311,7 +311,7 @@ float CanvasContext::setColorMode(ColorMode mode) { } switch (mColorMode) { case ColorMode::Hdr: - return 3.f; // TODO: Refine this number + return Properties::maxHdrHeadroomOn8bit; case ColorMode::Hdr10: return 10.f; default: diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index 7946baee200a..f939f523d3fa 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -280,7 +280,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand } case DO_NOTIFY_TV_MESSAGE: { SomeArgs args = (SomeArgs) msg.obj; - mTvInputSessionImpl.onTvMessageReceived((String) args.arg1, (Bundle) args.arg2); + mTvInputSessionImpl.onTvMessageReceived((Integer) args.arg1, (Bundle) args.arg2); break; } default: { diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 4e380c41ccdb..0d283fa87c19 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -1512,7 +1512,7 @@ public abstract class TvInputService extends Service { * See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on * how to parse this data. */ - public void onTvMessage(@NonNull @TvInputManager.TvMessageType String type, + public void onTvMessage(@TvInputManager.TvMessageType int type, @NonNull Bundle data) { } @@ -2065,7 +2065,7 @@ public abstract class TvInputService extends Service { onAdBufferReady(buffer); } - void onTvMessageReceived(String type, Bundle data) { + void onTvMessageReceived(int type, Bundle data) { onTvMessage(type, data); } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index dd607635b225..56324581c020 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -250,7 +250,7 @@ fun PrimarySelectionCard( leftButton = if (totalEntriesCount > 1) { { ActionButton( - stringResource(R.string.get_dialog_use_saved_passkey_for), + stringResource(R.string.get_dialog_title_sign_in_options), onMoreOptionSelected ) } 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 ca88f8da63c9..215f6b964ad3 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,7 +16,6 @@ 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 @@ -32,14 +31,14 @@ enum class LogCategory { } // Defines the log events in Spa. -enum class LogEvent(val action: Int) { +enum class LogEvent { // Page related events. - PAGE_ENTER(SettingsEnums.PAGE_VISIBLE), - PAGE_LEAVE(SettingsEnums.PAGE_HIDE), + PAGE_ENTER, + PAGE_LEAVE, // Entry related events. - ENTRY_CLICK(SettingsEnums.ACTION_SETTINGS_TILE_CLICK), - ENTRY_SWITCH(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE), + ENTRY_CLICK, + ENTRY_SWITCH, } internal const val LOG_DATA_DISPLAY_NAME = "name" diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java index a4c59ea4178d..a8eeec3c2f24 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java @@ -31,7 +31,7 @@ import java.io.IOException; /** * This class tracks changes for config/global/secure/system tables - * on a per user basis and updates shared memory regions which + * on a per-user basis and updates shared memory regions which * client processes can read to determine if their local caches are * stale. */ @@ -196,7 +196,9 @@ final class GenerationRegistry { if (backingStore == null) { try { if (mNumBackingStore >= NUM_MAX_BACKING_STORE) { - Slog.e(LOG_TAG, "Error creating backing store - at capacity"); + if (DEBUG) { + Slog.e(LOG_TAG, "Error creating backing store - at capacity"); + } return null; } backingStore = new MemoryIntArray(MAX_BACKING_STORE_SIZE); @@ -256,7 +258,9 @@ final class GenerationRegistry { + " on user:" + SettingsState.getUserIdFromKey(key)); } } else { - Slog.e(LOG_TAG, "Could not allocate generation index"); + if (DEBUG) { + Slog.e(LOG_TAG, "Could not allocate generation index"); + } } } return index; diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index a774dc9523e9..4290ca0d0982 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -924,7 +924,7 @@ android:showForAllUsers="true" android:finishOnTaskLaunch="true" android:launchMode="singleInstance" - android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden" + android:configChanges="screenLayout|keyboard|keyboardHidden|orientation" android:visibleToInstantApps="true"> </activity> diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index 00c0a0b3e7b3..e73afe74c03d 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -29,14 +29,15 @@ import com.android.systemui.plugins.ClockMetadata import com.android.systemui.plugins.ClockProvider import com.android.systemui.plugins.ClockProviderPlugin import com.android.systemui.plugins.ClockSettings +import com.android.systemui.plugins.PluginLifecycleManager import com.android.systemui.plugins.PluginListener import com.android.systemui.plugins.PluginManager import com.android.systemui.util.Assert +import java.util.concurrent.atomic.AtomicBoolean import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -private val TAG = ClockRegistry::class.simpleName!! private const val DEBUG = true private val KEY_TIMESTAMP = "appliedTimestamp" @@ -51,7 +52,10 @@ open class ClockRegistry( val handleAllUsers: Boolean, defaultClockProvider: ClockProvider, val fallbackClockId: ClockId = DEFAULT_CLOCK_ID, + val keepAllLoaded: Boolean, + val subTag: String, ) { + private val TAG = "${ClockRegistry::class.simpleName} ($subTag)" interface ClockChangeListener { // Called when the active clock changes fun onCurrentClockChanged() {} @@ -76,11 +80,85 @@ open class ClockRegistry( private val pluginListener = object : PluginListener<ClockProviderPlugin> { - override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) = - connectClocks(plugin) + override fun onPluginAttached(manager: PluginLifecycleManager<ClockProviderPlugin>) { + manager.loadPlugin() + } + + override fun onPluginLoaded( + plugin: ClockProviderPlugin, + pluginContext: Context, + manager: PluginLifecycleManager<ClockProviderPlugin> + ) { + var isClockListChanged = false + for (clock in plugin.getClocks()) { + val id = clock.clockId + var isNew = false + val info = + availableClocks.getOrPut(id) { + isNew = true + ClockInfo(clock, plugin, manager) + } + + if (isNew) { + isClockListChanged = true + onConnected(id) + } + + if (manager != info.manager) { + Log.e( + TAG, + "Clock Id conflict on load: $id is registered to another provider" + ) + continue + } + + info.provider = plugin + onLoaded(id) + } + + if (isClockListChanged) { + triggerOnAvailableClocksChanged() + } + verifyLoadedProviders() + } - override fun onPluginDisconnected(plugin: ClockProviderPlugin) = - disconnectClocks(plugin) + override fun onPluginUnloaded( + plugin: ClockProviderPlugin, + manager: PluginLifecycleManager<ClockProviderPlugin> + ) { + for (clock in plugin.getClocks()) { + val id = clock.clockId + val info = availableClocks[id] + if (info?.manager != manager) { + Log.e( + TAG, + "Clock Id conflict on unload: $id is registered to another provider" + ) + continue + } + info.provider = null + onUnloaded(id) + } + + verifyLoadedProviders() + } + + override fun onPluginDetached(manager: PluginLifecycleManager<ClockProviderPlugin>) { + val removed = mutableListOf<ClockId>() + availableClocks.entries.removeAll { + if (it.value.manager != manager) { + return@removeAll false + } + + removed.add(it.key) + return@removeAll true + } + + removed.forEach(::onDisconnected) + if (removed.size > 0) { + triggerOnAvailableClocksChanged() + } + } } private val userSwitchObserver = @@ -96,7 +174,8 @@ open class ClockRegistry( protected set(value) { if (field != value) { field = value - scope.launch(mainDispatcher) { onClockChanged { it.onCurrentClockChanged() } } + verifyLoadedProviders() + triggerOnCurrentClockChanged() } } @@ -168,9 +247,36 @@ open class ClockRegistry( Assert.isNotMainThread() } - private fun onClockChanged(func: (ClockChangeListener) -> Unit) { - assertMainThread() - clockChangeListeners.forEach(func) + private var isClockChanged = AtomicBoolean(false) + private fun triggerOnCurrentClockChanged() { + val shouldSchedule = isClockChanged.compareAndSet(false, true) + if (!shouldSchedule) { + return + } + + android.util.Log.e("HAWK", "triggerOnCurrentClockChanged") + scope.launch(mainDispatcher) { + assertMainThread() + android.util.Log.e("HAWK", "isClockChanged") + isClockChanged.set(false) + clockChangeListeners.forEach { it.onCurrentClockChanged() } + } + } + + private var isClockListChanged = AtomicBoolean(false) + private fun triggerOnAvailableClocksChanged() { + val shouldSchedule = isClockListChanged.compareAndSet(false, true) + if (!shouldSchedule) { + return + } + + android.util.Log.e("HAWK", "triggerOnAvailableClocksChanged") + scope.launch(mainDispatcher) { + assertMainThread() + android.util.Log.e("HAWK", "isClockListChanged") + isClockListChanged.set(false) + clockChangeListeners.forEach { it.onAvailableClocksChanged() } + } } public fun mutateSetting(mutator: (ClockSettings) -> ClockSettings) { @@ -190,7 +296,12 @@ open class ClockRegistry( } init { - connectClocks(defaultClockProvider) + // Register default clock designs + for (clock in defaultClockProvider.getClocks()) { + availableClocks[clock.clockId] = ClockInfo(clock, defaultClockProvider, null) + } + + // Something has gone terribly wrong if the default clock isn't present if (!availableClocks.containsKey(DEFAULT_CLOCK_ID)) { throw IllegalArgumentException( "$defaultClockProvider did not register clock at $DEFAULT_CLOCK_ID" @@ -244,59 +355,87 @@ open class ClockRegistry( } } - private fun connectClocks(provider: ClockProvider) { - var isAvailableChanged = false - val currentId = currentClockId - for (clock in provider.getClocks()) { - val id = clock.clockId - val current = availableClocks[id] - if (current != null) { - Log.e( - TAG, - "Clock Id conflict: $id is registered by both " + - "${provider::class.simpleName} and ${current.provider::class.simpleName}" - ) - continue - } + private var isVerifying = AtomicBoolean(false) + private fun verifyLoadedProviders() { + val shouldSchedule = isVerifying.compareAndSet(false, true) + if (!shouldSchedule) { + return + } - availableClocks[id] = ClockInfo(clock, provider) - isAvailableChanged = true - if (DEBUG) { - Log.i(TAG, "Added ${clock.clockId}") + scope.launch(bgDispatcher) { + if (keepAllLoaded) { + // Enforce that all plugins are loaded if requested + for ((_, info) in availableClocks) { + info.manager?.loadPlugin() + } + isVerifying.set(false) + return@launch } - if (currentId == id) { - if (DEBUG) { - Log.i(TAG, "Current clock ($currentId) was connected") + val currentClock = availableClocks[currentClockId] + if (currentClock == null) { + // Current Clock missing, load no plugins and use default + for ((_, info) in availableClocks) { + info.manager?.unloadPlugin() } - onClockChanged { it.onCurrentClockChanged() } + isVerifying.set(false) + return@launch } - } - if (isAvailableChanged) { - onClockChanged { it.onAvailableClocksChanged() } + val currentManager = currentClock.manager + currentManager?.loadPlugin() + + for ((_, info) in availableClocks) { + val manager = info.manager + if (manager != null && manager.isLoaded && currentManager != manager) { + manager.unloadPlugin() + } + } + isVerifying.set(false) } } - private fun disconnectClocks(provider: ClockProvider) { - var isAvailableChanged = false - val currentId = currentClockId - for (clock in provider.getClocks()) { - availableClocks.remove(clock.clockId) - isAvailableChanged = true + private fun onConnected(clockId: ClockId) { + if (DEBUG) { + Log.i(TAG, "Connected $clockId") + } + if (currentClockId == clockId) { if (DEBUG) { - Log.i(TAG, "Removed ${clock.clockId}") + Log.i(TAG, "Current clock ($clockId) was connected") } + } + } - if (currentId == clock.clockId) { - Log.w(TAG, "Current clock ($currentId) was disconnected") - onClockChanged { it.onCurrentClockChanged() } - } + private fun onLoaded(clockId: ClockId) { + if (DEBUG) { + Log.i(TAG, "Loaded $clockId") + } + + if (currentClockId == clockId) { + Log.i(TAG, "Current clock ($clockId) was loaded") + triggerOnCurrentClockChanged() + } + } + + private fun onUnloaded(clockId: ClockId) { + if (DEBUG) { + Log.i(TAG, "Unloaded $clockId") + } + + if (currentClockId == clockId) { + Log.w(TAG, "Current clock ($clockId) was unloaded") + triggerOnCurrentClockChanged() + } + } + + private fun onDisconnected(clockId: ClockId) { + if (DEBUG) { + Log.i(TAG, "Disconnected $clockId") } - if (isAvailableChanged) { - onClockChanged { it.onAvailableClocksChanged() } + if (currentClockId == clockId) { + Log.w(TAG, "Current clock ($clockId) was disconnected") } } @@ -345,6 +484,7 @@ open class ClockRegistry( private data class ClockInfo( val metadata: ClockMetadata, - val provider: ClockProvider, + var provider: ClockProvider?, + val manager: PluginLifecycleManager<ClockProviderPlugin>?, ) } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java index 70b5d739ea7c..b7088d5d3d23 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java @@ -50,6 +50,13 @@ public interface StatusBarStateController { boolean isPulsing(); /** + * Is device dreaming. This method is more inclusive than + * {@link android.service.dreams.IDreamManager.isDreaming}, as it will return true during the + * dream's wake-up phase. + */ + boolean isDreaming(); + + /** * Adds a state listener */ void addCallback(StateListener listener); diff --git a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java new file mode 100644 index 000000000000..cc6a46fa7d6b --- /dev/null +++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java @@ -0,0 +1,48 @@ +/* + * 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.plugins; + +/** + * Provides the ability for consumers to control plugin lifecycle. + * + * @param <T> is the target plugin type + */ +public interface PluginLifecycleManager<T extends Plugin> { + /** Returns the currently loaded plugin instance (if plugin is loaded) */ + T getPlugin(); + + /** returns true if the plugin is currently loaded */ + default boolean isLoaded() { + return getPlugin() != null; + } + + /** + * Loads and creates the plugin instance if it does not exist. + * + * This will trigger {@link PluginListener#onPluginLoaded} with the new instance if it did not + * already exist. + */ + void loadPlugin(); + + /** + * Unloads and destroys the plugin instance if it exists. + * + * This will trigger {@link PluginListener#onPluginUnloaded} if a concrete plugin instance + * existed when this call was made. + */ + void unloadPlugin(); +} diff --git a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginListener.java b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginListener.java index b488d2a84baa..c5f503216101 100644 --- a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginListener.java +++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginListener.java @@ -17,7 +17,32 @@ package com.android.systemui.plugins; import android.content.Context; /** - * Interface for listening to plugins being connected. + * Interface for listening to plugins being connected and disconnected. + * + * The call order for a plugin is + * 1) {@link #onPluginAttached} + * Called when a new plugin is added to the device, or an existing plugin was replaced by + * the package manager. Will only be called once per package manager event. If multiple + * non-conflicting packages which have the same plugin interface are installed on the + * device, then this method can be called multiple times with different instances of + * {@link PluginLifecycleManager} (as long as `allowMultiple` was set to true when the + * listener was registered with {@link PluginManager#addPluginListener}). + * 2) {@link #onPluginLoaded} + * Called whenever a new instance of the plugin object is created and ready for use. Can be + * called multiple times per {@link PluginLifecycleManager}, but will always pass a newly + * created plugin object. {@link #onPluginUnloaded} with the previous plugin object will + * be called before another call to {@link #onPluginLoaded} is made. This method will be + * called once automatically after {@link #onPluginAttached}. Besides the initial call, + * {@link #onPluginLoaded} will occur due to {@link PluginLifecycleManager#loadPlugin}. + * 3) {@link #onPluginUnloaded} + * Called when a request to unload the plugin has been received. This can be triggered from + * a related call to {@link PluginLifecycleManager#unloadPlugin} or for any reason that + * {@link #onPluginDetached} would be triggered. + * 4) {@link #onPluginDetached} + * Called when the package is removed from the device, disabled, or replaced due to an + * external trigger. These are events from the android package manager. + * + * @param <T> is the target plugin type */ public interface PluginListener<T extends Plugin> { /** @@ -25,14 +50,69 @@ public interface PluginListener<T extends Plugin> { * This may be called multiple times if multiple plugins are allowed. * It may also be called in the future if the plugin package changes * and needs to be reloaded. + * + * @deprecated Migrate to {@link #onPluginLoaded} or {@link #onPluginAttached} + */ + @Deprecated + default void onPluginConnected(T plugin, Context pluginContext) { + // Optional + } + + /** + * Called when the plugin is first attached to the host application. {@link #onPluginLoaded} + * will be automatically called as well when first attached. This may be called multiple times + * if multiple plugins are allowed. It may also be called in the future if the plugin package + * changes and needs to be reloaded. Each call to {@link #onPluginAttached} will provide a new + * or different {@link PluginLifecycleManager}. */ - void onPluginConnected(T plugin, Context pluginContext); + default void onPluginAttached(PluginLifecycleManager<T> manager) { + // Optional + } /** * Called when a plugin has been uninstalled/updated and should be removed * from use. + * + * @deprecated Migrate to {@link #onPluginDetached} or {@link #onPluginUnloaded} */ + @Deprecated default void onPluginDisconnected(T plugin) { // Optional. } -} + + /** + * Called when the plugin has been detached from the host application. Implementers should no + * longer attempt to reload it via this {@link PluginLifecycleManager}. If the package was + * updated and not removed, then {@link #onPluginAttached} will be called again when the updated + * package is available. + */ + default void onPluginDetached(PluginLifecycleManager<T> manager) { + // Optional. + } + + /** + * Called when the plugin is loaded into the host's process and is available for use. This can + * happen several times if clients are using {@link PluginLifecycleManager} to manipulate a + * plugin's load state. Each call to {@link #onPluginLoaded} will have a matched call to + * {@link #onPluginUnloaded} when that plugin object should no longer be used. + */ + default void onPluginLoaded( + T plugin, + Context pluginContext, + PluginLifecycleManager<T> manager + ) { + // Optional, default to deprecated version + onPluginConnected(plugin, pluginContext); + } + + /** + * Called when the plugin should no longer be used. Listeners should clean up all references to + * the relevant plugin so that it can be garbage collected. If the plugin object is required in + * the future a call can be made to {@link PluginLifecycleManager#loadPlugin} to create a new + * plugin object and trigger {@link #onPluginLoaded}. + */ + default void onPluginUnloaded(T plugin, PluginLifecycleManager<T> manager) { + // Optional, default to deprecated version + onPluginDisconnected(plugin); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml index 9d914197c05a..85b6e8dc12b3 100644 --- a/packages/SystemUI/res/layout/media_session_view.xml +++ b/packages/SystemUI/res/layout/media_session_view.xml @@ -97,7 +97,8 @@ android:background="@drawable/qs_media_light_source" android:forceHasOverlappingRendering="false" android:layout_width="wrap_content" - android:layout_height="@dimen/min_clickable_item_size" + android:minHeight="@dimen/min_clickable_item_size" + android:layout_height="wrap_content" android:layout_marginStart="@dimen/qs_center_guideline_padding" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/packages/SystemUI/res/layout/multi_shade.xml b/packages/SystemUI/res/layout/multi_shade.xml new file mode 100644 index 000000000000..78ff5f03bea2 --- /dev/null +++ b/packages/SystemUI/res/layout/multi_shade.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. + ~ + --> + +<com.android.systemui.multishade.ui.view.MultiShadeView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" /> diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index 4abc1769ab54..fe9542b7aed6 100644 --- a/packages/SystemUI/res/layout/super_notification_shade.xml +++ b/packages/SystemUI/res/layout/super_notification_shade.xml @@ -110,6 +110,13 @@ android:clipChildren="false" android:clipToPadding="false" /> + <ViewStub + android:id="@+id/multi_shade_stub" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:inflatedId="@+id/multi_shade" + android:layout="@layout/multi_shade" /> + <com.android.systemui.biometrics.AuthRippleView android:id="@+id/auth_ripple" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index 6354752e1b22..763930db1d55 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -30,9 +30,6 @@ <bool name="flag_charging_ripple">false</bool> - <!-- Whether to show chipbar UI whenever the device is unlocked by ActiveUnlock. --> - <bool name="flag_active_unlock_chipbar">true</bool> - <!-- Whether the user switcher chip shows in the status bar. When true, the multi user avatar will no longer show on the lockscreen --> <bool name="flag_user_switcher_chip">false</bool> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index a02c429e0229..ebf0f8ecd48c 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -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"><xliff:g id="appName" example="My app">%s</xliff:g>can choose which controls and content show 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] --> diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt index 97665145ce76..dedf0a7a742d 100644 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt @@ -53,6 +53,7 @@ import kotlinx.coroutines.runBlocking fun View.captureToBitmap(window: Window? = null): ListenableFuture<Bitmap> { val bitmapFuture: ResolvableFuture<Bitmap> = ResolvableFuture.create() val mainExecutor = HandlerExecutor(Handler(Looper.getMainLooper())) + val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false // disable drawing again if necessary once work is complete if (!HardwareRendererCompat.isDrawingEnabled()) { @@ -61,8 +62,12 @@ fun View.captureToBitmap(window: Window? = null): ListenableFuture<Bitmap> { } mainExecutor.execute { - val forceRedrawFuture = forceRedraw() - forceRedrawFuture.addListener({ generateBitmap(bitmapFuture, window) }, mainExecutor) + if (isRobolectric) { + generateBitmap(bitmapFuture) + } else { + val forceRedrawFuture = forceRedraw() + forceRedrawFuture.addListener({ generateBitmap(bitmapFuture, window) }, mainExecutor) + } } return bitmapFuture diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt index 2d47356f2ce1..f96d1e3d7fef 100644 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt @@ -19,6 +19,7 @@ package com.android.systemui.testing.screenshot import android.app.Activity import android.app.Dialog import android.graphics.Bitmap +import android.os.Build import android.view.View import android.view.ViewGroup import android.view.ViewGroup.LayoutParams @@ -26,6 +27,7 @@ import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import androidx.activity.ComponentActivity import androidx.test.ext.junit.rules.ActivityScenarioRule +import java.util.concurrent.TimeUnit import org.junit.Assert.assertEquals import org.junit.rules.RuleChain import org.junit.rules.TestRule @@ -54,14 +56,14 @@ open class ViewScreenshotTestRule( ) ) private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java) - private val delegateRule = - RuleChain.outerRule(colorsRule) - .around(deviceEmulationRule) - .around(screenshotRule) - .around(activityRule) + private val roboRule = + RuleChain.outerRule(deviceEmulationRule).around(screenshotRule).around(activityRule) + private val delegateRule = RuleChain.outerRule(colorsRule).around(roboRule) + private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false override fun apply(base: Statement, description: Description): Statement { - return delegateRule.apply(base, description) + val ruleToApply = if (isRobolectric) roboRule else delegateRule + return ruleToApply.apply(base, description) } protected fun takeScreenshot( @@ -94,7 +96,12 @@ open class ViewScreenshotTestRule( contentView = content.getChildAt(0) } - return contentView?.toBitmap() ?: error("contentView is null") + return if (isRobolectric) { + contentView?.captureToBitmap()?.get(10, TimeUnit.SECONDS) + ?: error("timeout while trying to capture view to bitmap") + } else { + contentView?.toBitmap() ?: error("contentView is null") + } } /** diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java index cc3d7a8931b0..3d05542116e0 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java @@ -210,12 +210,12 @@ public class PluginActionManager<T extends Plugin> { private void onPluginConnected(PluginInstance<T> pluginInstance) { if (DEBUG) Log.d(TAG, "onPluginConnected"); PluginPrefs.setHasPlugins(mContext); - pluginInstance.onCreate(mContext, mListener); + pluginInstance.onCreate(); } private void onPluginDisconnected(PluginInstance<T> pluginInstance) { if (DEBUG) Log.d(TAG, "onPluginDisconnected"); - pluginInstance.onDestroy(mListener); + pluginInstance.onDestroy(); } private void queryAll() { @@ -312,7 +312,7 @@ public class PluginActionManager<T extends Plugin> { try { return mPluginInstanceFactory.create( mContext, appInfo, component, - mPluginClass); + mPluginClass, mListener); } catch (InvalidVersionException e) { reportInvalidVersion(component, component.getClassName(), e); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java index 2f84602089e0..016d573930e8 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java @@ -21,13 +21,16 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.text.TextUtils; -import android.util.ArrayMap; import android.util.Log; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginFragment; +import com.android.systemui.plugins.PluginLifecycleManager; import com.android.systemui.plugins.PluginListener; import dalvik.system.PathClassLoader; @@ -35,7 +38,7 @@ import dalvik.system.PathClassLoader; import java.io.File; import java.util.ArrayList; import java.util.List; -import java.util.Map; +import java.util.function.Supplier; /** * Contains a single instantiation of a Plugin. @@ -45,42 +48,102 @@ import java.util.Map; * * @param <T> The type of plugin that this contains. */ -public class PluginInstance<T extends Plugin> { +public class PluginInstance<T extends Plugin> implements PluginLifecycleManager { private static final String TAG = "PluginInstance"; - private static final Map<String, ClassLoader> sClassLoaders = new ArrayMap<>(); - private final Context mPluginContext; - private final VersionInfo mVersionInfo; + private final Context mAppContext; + private final PluginListener<T> mListener; private final ComponentName mComponentName; - private final T mPlugin; + private final PluginFactory<T> mPluginFactory; + + private Context mPluginContext; + private T mPlugin; /** */ - public PluginInstance(ComponentName componentName, T plugin, Context pluginContext, - VersionInfo versionInfo) { + public PluginInstance( + Context appContext, + PluginListener<T> listener, + ComponentName componentName, + PluginFactory<T> pluginFactory, + @Nullable T plugin) { + mAppContext = appContext; + mListener = listener; mComponentName = componentName; + mPluginFactory = pluginFactory; mPlugin = plugin; - mPluginContext = pluginContext; - mVersionInfo = versionInfo; + + if (mPlugin != null) { + mPluginContext = mPluginFactory.createPluginContext(); + } } /** Alerts listener and plugin that the plugin has been created. */ - public void onCreate(Context appContext, PluginListener<T> listener) { + public void onCreate() { + mListener.onPluginAttached(this); + if (mPlugin == null) { + loadPlugin(); + } else { + if (!(mPlugin instanceof PluginFragment)) { + // Only call onCreate for plugins that aren't fragments, as fragments + // will get the onCreate as part of the fragment lifecycle. + mPlugin.onCreate(mAppContext, mPluginContext); + } + mListener.onPluginLoaded(mPlugin, mPluginContext, this); + } + } + + /** Alerts listener and plugin that the plugin is being shutdown. */ + public void onDestroy() { + unloadPlugin(); + mListener.onPluginDetached(this); + } + + /** Returns the current plugin instance (if it is loaded). */ + @Nullable + public T getPlugin() { + return mPlugin; + } + + /** + * Loads and creates the plugin if it does not exist. + */ + public void loadPlugin() { + if (mPlugin != null) { + return; + } + + mPlugin = mPluginFactory.createPlugin(); + mPluginContext = mPluginFactory.createPluginContext(); + if (mPlugin == null || mPluginContext == null) { + return; + } + if (!(mPlugin instanceof PluginFragment)) { // Only call onCreate for plugins that aren't fragments, as fragments // will get the onCreate as part of the fragment lifecycle. - mPlugin.onCreate(appContext, mPluginContext); + mPlugin.onCreate(mAppContext, mPluginContext); } - listener.onPluginConnected(mPlugin, mPluginContext); + mListener.onPluginLoaded(mPlugin, mPluginContext, this); } - /** Alerts listener and plugin that the plugin is being shutdown. */ - public void onDestroy(PluginListener<T> listener) { - listener.onPluginDisconnected(mPlugin); + /** + * Unloads and destroys the current plugin instance if it exists. + * + * This will free the associated memory if there are not other references. + */ + public void unloadPlugin() { + if (mPlugin == null) { + return; + } + + mListener.onPluginUnloaded(mPlugin, this); if (!(mPlugin instanceof PluginFragment)) { // Only call onDestroy for plugins that aren't fragments, as fragments // will get the onDestroy as part of the fragment lifecycle. mPlugin.onDestroy(); } + mPlugin = null; + mPluginContext = null; } /** @@ -89,7 +152,7 @@ public class PluginInstance<T extends Plugin> { * It does this by string comparison of the class names. **/ public boolean containsPluginClass(Class pluginClass) { - return mPlugin.getClass().getName().equals(pluginClass.getName()); + return mComponentName.getClassName().equals(pluginClass.getName()); } public ComponentName getComponentName() { @@ -101,7 +164,7 @@ public class PluginInstance<T extends Plugin> { } public VersionInfo getVersionInfo() { - return mVersionInfo; + return mPluginFactory.checkVersion(mPlugin); } @VisibleForTesting @@ -134,21 +197,20 @@ public class PluginInstance<T extends Plugin> { Context context, ApplicationInfo appInfo, ComponentName componentName, - Class<T> pluginClass) + Class<T> pluginClass, + PluginListener<T> listener) throws PackageManager.NameNotFoundException, ClassNotFoundException, InstantiationException, IllegalAccessException { - ClassLoader classLoader = getClassLoader(appInfo, mBaseClassLoader); - Context pluginContext = new PluginActionManager.PluginContextWrapper( - context.createApplicationContext(appInfo, 0), classLoader); - Class<T> instanceClass = (Class<T>) Class.forName( - componentName.getClassName(), true, classLoader); + PluginFactory<T> pluginFactory = new PluginFactory<T>( + context, mInstanceFactory, appInfo, componentName, mVersionChecker, pluginClass, + () -> getClassLoader(appInfo, mBaseClassLoader)); // TODO: Only create the plugin before version check if we need it for // legacy version check. - T instance = (T) mInstanceFactory.create(instanceClass); - VersionInfo version = mVersionChecker.checkVersion( - instanceClass, pluginClass, instance); - return new PluginInstance<T>(componentName, instance, pluginContext, version); + T instance = pluginFactory.createPlugin(); + pluginFactory.checkVersion(instance); + return new PluginInstance<T>( + context, listener, componentName, pluginFactory, instance); } private boolean isPluginPackagePrivileged(String packageName) { @@ -179,9 +241,6 @@ public class PluginInstance<T extends Plugin> { + appInfo.sourceDir + ", pkg: " + appInfo.packageName); return null; } - if (sClassLoaders.containsKey(appInfo.packageName)) { - return sClassLoaders.get(appInfo.packageName); - } List<String> zipPaths = new ArrayList<>(); List<String> libPaths = new ArrayList<>(); @@ -190,13 +249,20 @@ public class PluginInstance<T extends Plugin> { TextUtils.join(File.pathSeparator, zipPaths), TextUtils.join(File.pathSeparator, libPaths), getParentClassLoader(baseClassLoader)); - sClassLoaders.put(appInfo.packageName, classLoader); return classLoader; } } /** Class that compares a plugin class against an implementation for version matching. */ - public static class VersionChecker { + public interface VersionChecker { + /** Compares two plugin classes. */ + <T extends Plugin> VersionInfo checkVersion( + Class<T> instanceClass, Class<T> pluginClass, Plugin plugin); + } + + /** Class that compares a plugin class against an implementation for version matching. */ + public static class VersionCheckerImpl implements VersionChecker { + @Override /** Compares two plugin classes. */ public <T extends Plugin> VersionInfo checkVersion( Class<T> instanceClass, Class<T> pluginClass, Plugin plugin) { @@ -204,7 +270,7 @@ public class PluginInstance<T extends Plugin> { VersionInfo instanceVersion = new VersionInfo().addClass(instanceClass); if (instanceVersion.hasVersionInfo()) { pluginVersion.checkVersion(instanceVersion); - } else { + } else if (plugin != null) { int fallbackVersion = plugin.getVersion(); if (fallbackVersion != pluginVersion.getDefaultVersion()) { throw new VersionInfo.InvalidVersionException("Invalid legacy version", false); @@ -225,4 +291,74 @@ public class PluginInstance<T extends Plugin> { return (T) cls.newInstance(); } } + + /** + * Instanced wrapper of InstanceFactory + * + * @param <T> is the type of the plugin object to be built + **/ + public static class PluginFactory<T extends Plugin> { + private final Context mContext; + private final InstanceFactory<?> mInstanceFactory; + private final ApplicationInfo mAppInfo; + private final ComponentName mComponentName; + private final VersionChecker mVersionChecker; + private final Class<T> mPluginClass; + private final Supplier<ClassLoader> mClassLoaderFactory; + + public PluginFactory( + Context context, + InstanceFactory<?> instanceFactory, + ApplicationInfo appInfo, + ComponentName componentName, + VersionChecker versionChecker, + Class<T> pluginClass, + Supplier<ClassLoader> classLoaderFactory) { + mContext = context; + mInstanceFactory = instanceFactory; + mAppInfo = appInfo; + mComponentName = componentName; + mVersionChecker = versionChecker; + mPluginClass = pluginClass; + mClassLoaderFactory = classLoaderFactory; + } + + /** Creates the related plugin object from the factory */ + public T createPlugin() { + try { + ClassLoader loader = mClassLoaderFactory.get(); + Class<T> instanceClass = (Class<T>) Class.forName( + mComponentName.getClassName(), true, loader); + return (T) mInstanceFactory.create(instanceClass); + } catch (ClassNotFoundException ex) { + Log.e(TAG, "Failed to load plugin", ex); + } catch (IllegalAccessException ex) { + Log.e(TAG, "Failed to load plugin", ex); + } catch (InstantiationException ex) { + Log.e(TAG, "Failed to load plugin", ex); + } + return null; + } + + /** Creates a context wrapper for the plugin */ + public Context createPluginContext() { + try { + ClassLoader loader = mClassLoaderFactory.get(); + return new PluginActionManager.PluginContextWrapper( + mContext.createApplicationContext(mAppInfo, 0), loader); + } catch (NameNotFoundException ex) { + Log.e(TAG, "Failed to create plugin context", ex); + } + return null; + } + + /** Check Version and create VersionInfo for instance */ + public VersionInfo checkVersion(T instance) { + if (instance == null) { + instance = createPlugin(); + } + return mVersionChecker.checkVersion( + (Class<T>) instance.getClass(), mPluginClass, instance); + } + } } diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java index b1a83fbda7de..6e98a1805d62 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java @@ -60,7 +60,9 @@ public abstract class ClockRegistryModule { featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS), /* handleAllUsers= */ true, new DefaultClockProvider(context, layoutInflater, resources), - context.getString(R.string.lockscreen_clock_id_fallback)); + context.getString(R.string.lockscreen_clock_id_fallback), + /* keepAllLoaded = */ false, + /* subTag = */ "System"); registry.registerListeners(); return registry; } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt index bf0a69296dfd..224eb1ca409a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt @@ -20,6 +20,8 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.pm.ActivityInfo +import android.content.res.Configuration import android.os.Bundle import android.os.RemoteException import android.service.dreams.IDreamManager @@ -57,9 +59,11 @@ class ControlsActivity @Inject constructor( private lateinit var parent: ViewGroup private lateinit var broadcastReceiver: BroadcastReceiver private var mExitToDream: Boolean = false + private lateinit var lastConfiguration: Configuration override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + lastConfiguration = resources.configuration if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) { window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY) } @@ -92,6 +96,14 @@ class ControlsActivity @Inject constructor( initBroadcastReceiver() } + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + if (lastConfiguration.diff(newConfig) and ActivityInfo.CONFIG_ORIENTATION != 0 ) { + uiController.onOrientationChange() + } + lastConfiguration = newConfig + } + override fun onStart() { super.onStart() 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 0d5311752ab9..3ecf4236656d 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt @@ -64,6 +64,8 @@ interface ControlsUiController { * This element will be the one that appears when the user first opens the controls activity. */ fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem + + fun onOrientationChange() } 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 6cbf062ef340..ee12db8d07b1 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -124,6 +124,7 @@ class ControlsUiControllerImpl @Inject constructor ( } private var selectedItem: SelectedItem = SelectedItem.EMPTY_SELECTION + private var selectionItem: SelectionItem? = null private lateinit var allStructures: List<StructureInfo> private val controlsById = mutableMapOf<ControlKey, ControlWithState>() private val controlViewsById = mutableMapOf<ControlKey, ControlViewHolder>() @@ -230,6 +231,7 @@ class ControlsUiControllerImpl @Inject constructor ( this.overflowMenuAdapter = null hidden = false retainCache = false + selectionItem = null controlActionCoordinator.activityContext = activityContext @@ -272,7 +274,7 @@ class ControlsUiControllerImpl @Inject constructor ( } } - private fun reload(parent: ViewGroup) { + private fun reload(parent: ViewGroup, dismissTaskView: Boolean = true) { if (hidden) return controlsListingController.get().removeCallback(listingCallback) @@ -425,6 +427,7 @@ class ControlsUiControllerImpl @Inject constructor ( } else { Log.w(ControlsUiController.TAG, "Not TaskViewFactory to display panel $selectionItem") } + this.selectionItem = selectionItem bgExecutor.execute { val intent = Intent(Intent.ACTION_MAIN) @@ -657,6 +660,7 @@ class ControlsUiControllerImpl @Inject constructor ( val maxColumns = ControlAdapter.findMaxColumns(activityContext.resources) val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup + listView.removeAllViews() var lastRow: ViewGroup = createRow(inflater, listView) selectedStructure.controls.forEach { val key = ControlKey(selectedStructure.componentName, it.controlId) @@ -804,6 +808,15 @@ class ControlsUiControllerImpl @Inject constructor ( } } + override fun onOrientationChange() { + selectionItem?.let { + when (selectedItem) { + is SelectedItem.StructureItem -> createListView(it) + is SelectedItem.PanelItem -> taskViewController?.refreshBounds() ?: reload(parent) + } + } ?: reload(parent) + } + private fun createRow(inflater: LayoutInflater, listView: ViewGroup): ViewGroup { val row = inflater.inflate(R.layout.controls_row, listView, false) as ViewGroup listView.addView(row) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt index 78e87cafc4f2..1f89c917186a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt @@ -37,7 +37,7 @@ class PanelTaskViewController( private val activityContext: Context, private val uiExecutor: Executor, private val pendingIntent: PendingIntent, - private val taskView: TaskView, + val taskView: TaskView, private val hide: () -> Unit = {} ) { @@ -108,6 +108,10 @@ class PanelTaskViewController( } } + fun refreshBounds() { + taskView.onLocationChanged() + } + fun dismiss() { taskView.release() } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 661b2b6daf64..c0e11336b46f 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -162,12 +162,6 @@ object Flags { val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES = releasedFlag(216, "customizable_lock_screen_quick_affordances") - /** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */ - // TODO(b/256513609): Tracking Bug - @JvmField - val ACTIVE_UNLOCK_CHIPBAR = - resourceBooleanFlag(217, R.bool.flag_active_unlock_chipbar, "active_unlock_chipbar") - /** * Migrates control of the LightRevealScrim's reveal effect and amount from legacy code to the * new KeyguardTransitionRepository. @@ -284,8 +278,7 @@ object Flags { /** Enables new QS Edit Mode visual refresh */ // TODO(b/269787742): Tracking Bug @JvmField - val ENABLE_NEW_QS_EDIT_MODE = - unreleasedFlag(510, "enable_new_qs_edit_mode", teamfood = false) + val ENABLE_NEW_QS_EDIT_MODE = unreleasedFlag(510, "enable_new_qs_edit_mode", teamfood = false) // 600- status bar @@ -680,4 +673,9 @@ object Flags { @JvmField val LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION = unreleasedFlag(2602, "large_shade_granular_alpha_interpolation", teamfood = true) + + // TODO(b/272805037): Tracking Bug + @JvmField + val ADVANCED_VPN_ENABLED = unreleasedFlag(2800, name = "AdvancedVpn__enable_feature", + namespace = "vpn", teamfood = false) } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index fab8c068b2a7..78082c3eb3c6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -225,8 +225,10 @@ open class MediaTttChipControllerReceiver @Inject constructor( val iconRippleView: ReceiverChipRippleView = view.requireViewById(R.id.icon_glow_ripple) val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple) val translationYBy = getTranslationAmount() + // Expand ripple before translating icon container to make sure both views have same bounds. + rippleController.expandToInProgressState(rippleView, iconRippleView) // Make the icon container view starts animation from bottom of the screen. - iconContainerView.translationY += rippleController.getReceiverIconSize() + iconContainerView.translationY = rippleController.getReceiverIconSize().toFloat() animateViewTranslationAndFade( iconContainerView, translationYBy = -1 * translationYBy, @@ -235,7 +237,6 @@ open class MediaTttChipControllerReceiver @Inject constructor( ) { animateBouncingView(iconContainerView, translationYBy * BOUNCE_TRANSLATION_RATIO) } - rippleController.expandToInProgressState(rippleView, iconRippleView) } override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) { @@ -293,7 +294,7 @@ open class MediaTttChipControllerReceiver @Inject constructor( /** Returns the amount that the chip will be translated by in its intro animation. */ private fun getTranslationAmount(): Float { - return rippleController.getRippleSize() * 0.5f + return rippleController.getReceiverIconSize() * 2f } private fun View.getAppIconView(): CachingIconView { diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt new file mode 100644 index 000000000000..aecec39c5c07 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt @@ -0,0 +1,71 @@ +/* + * 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.multishade.ui.view + +import android.content.Context +import android.util.AttributeSet +import android.widget.FrameLayout +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.compose.ComposeFacade +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor +import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel +import com.android.systemui.util.time.SystemClock +import kotlinx.coroutines.launch + +/** + * View that hosts the multi-shade system and acts as glue between legacy code and the + * implementation. + */ +class MultiShadeView( + context: Context, + attrs: AttributeSet?, +) : + FrameLayout( + context, + attrs, + ) { + + fun init( + interactor: MultiShadeInteractor, + clock: SystemClock, + ) { + repeatWhenAttached { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + addView( + ComposeFacade.createMultiShadeView( + context = context, + viewModel = + MultiShadeViewModel( + viewModelScope = this, + interactor = interactor, + ), + clock = clock, + ) + ) + } + + // Here when destroyed. + removeAllViews() + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java index 95f14190537f..fbf1a0e46ce3 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java @@ -73,7 +73,7 @@ public abstract class PluginsModule { return new PluginInstance.Factory( PluginModule.class.getClassLoader(), new PluginInstance.InstanceFactory<>(), - new PluginInstance.VersionChecker(), + new PluginInstance.VersionCheckerImpl(), privilegedPlugins, isDebug); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index baa812c63aa7..584d27f84ceb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -635,7 +635,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca && mLastKeyguardAndExpanded == onKeyguardAndExpanded && mLastViewHeight == currentHeight && mLastHeaderTranslation == headerTranslation - && mSquishinessFraction == squishinessFraction) { + && mSquishinessFraction == squishinessFraction + && mLastPanelFraction == panelExpansionFraction) { return; } mLastHeaderTranslation = headerTranslation; diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 8a3ecc6afaac..0748bcbf020c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -21,7 +21,6 @@ import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; - import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY; @@ -713,6 +712,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } public void startConnectionToCurrentUser() { + Log.v(TAG_OPS, "startConnectionToCurrentUser: connection is restarted"); if (mHandler.getLooper() != Looper.myLooper()) { mHandler.post(mConnectionRunnable); } else { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 06426b3e5466..30865aa0c45d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -3581,15 +3581,12 @@ public final class NotificationPanelViewController implements Dumpable { } private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) { - // don't fling while in keyguard to avoid jump in shade expand animation - boolean fullyExpandedInKeyguard = mBarState == KEYGUARD && mExpandedFraction >= 1.0; mTrackingPointer = -1; mAmbientState.setSwipingUp(false); - if (!fullyExpandedInKeyguard && ((mTracking && mTouchSlopExceeded) - || Math.abs(x - mInitialExpandX) > mTouchSlop + if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop || Math.abs(y - mInitialExpandY) > mTouchSlop || (!isFullyExpanded() && !isFullyCollapsed()) - || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel)) { + || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) { mVelocityTracker.computeCurrentVelocity(1000); float vel = mVelocityTracker.getYVelocity(); float vectorVel = (float) Math.hypot( @@ -3637,9 +3634,9 @@ public final class NotificationPanelViewController implements Dumpable { if (mUpdateFlingOnLayout) { mUpdateFlingVelocity = vel; } - } else if (fullyExpandedInKeyguard || (!mCentralSurfaces.isBouncerShowing() + } else if (!mCentralSurfaces.isBouncerShowing() && !mAlternateBouncerInteractor.isVisibleState() - && !mKeyguardStateController.isKeyguardGoingAway())) { + && !mKeyguardStateController.isKeyguardGoingAway()) { onEmptySpaceClick(); onTrackingStopped(true); } @@ -3782,10 +3779,10 @@ public final class NotificationPanelViewController implements Dumpable { mHeightAnimator.end(); } } - mQsController.setShadeExpandedHeight(mExpandedHeight); - mExpansionDragDownAmountPx = h; mExpandedFraction = Math.min(1f, maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight); + mQsController.setShadeExpansion(mExpandedHeight, mExpandedFraction); + mExpansionDragDownAmountPx = h; mAmbientState.setExpansionFraction(mExpandedFraction); onHeightUpdated(mExpandedHeight); updatePanelExpansionAndVisibility(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 289908136e7e..a716a6ea55fb 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -23,7 +23,6 @@ import android.app.StatusBarManager; import android.media.AudioManager; import android.media.session.MediaSessionLegacyHelper; import android.os.PowerManager; -import android.os.SystemClock; import android.util.Log; import android.view.GestureDetector; import android.view.InputDevice; @@ -31,6 +30,7 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewStub; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.AuthKeyguardMessageArea; @@ -39,8 +39,10 @@ import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.R; import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.compose.ComposeFacade; 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.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; @@ -49,6 +51,8 @@ import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; +import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor; +import com.android.systemui.multishade.ui.view.MultiShadeView; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationInsetsController; @@ -63,11 +67,13 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.window.StatusBarWindowStateController; +import com.android.systemui.util.time.SystemClock; import java.io.PrintWriter; import java.util.function.Consumer; import javax.inject.Inject; +import javax.inject.Provider; /** * Controller for {@link NotificationShadeWindowView}. @@ -115,6 +121,7 @@ public class NotificationShadeWindowViewController { mIsOcclusionTransitionRunning = step.getTransitionState() == TransitionState.RUNNING; }; + private final SystemClock mClock; @Inject public NotificationShadeWindowViewController( @@ -142,7 +149,9 @@ public class NotificationShadeWindowViewController { UdfpsOverlayInteractor udfpsOverlayInteractor, KeyguardTransitionInteractor keyguardTransitionInteractor, PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + Provider<MultiShadeInteractor> multiShadeInteractorProvider, + SystemClock clock) { mLockscreenShadeTransitionController = transitionController; mFalsingCollector = falsingCollector; mStatusBarStateController = statusBarStateController; @@ -175,6 +184,16 @@ public class NotificationShadeWindowViewController { collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(), mLockscreenToDreamingTransition); + + mClock = clock; + if (ComposeFacade.INSTANCE.isComposeAvailable() + && featureFlags.isEnabled(Flags.DUAL_SHADE)) { + final ViewStub multiShadeViewStub = mView.findViewById(R.id.multi_shade_stub); + if (multiShadeViewStub != null) { + final MultiShadeView multiShadeView = (MultiShadeView) multiShadeViewStub.inflate(); + multiShadeView.init(multiShadeInteractorProvider.get(), clock); + } + } } /** @@ -269,7 +288,7 @@ public class NotificationShadeWindowViewController { mLockIconViewController.onTouchEvent( ev, () -> mService.wakeUpIfDozing( - SystemClock.uptimeMillis(), + mClock.uptimeMillis(), mView, "LOCK_ICON_TOUCH", PowerManager.WAKE_REASON_GESTURE) @@ -453,7 +472,7 @@ public class NotificationShadeWindowViewController { public void cancelCurrentTouch() { if (mTouchActive) { - final long now = SystemClock.uptimeMillis(); + final long now = mClock.uptimeMillis(); final MotionEvent event; if (mIsTrackpadGestureBackEnabled) { event = MotionEvent.obtain(mDownEvent); diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index df8ae50682fc..9f467074d473 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -351,7 +351,6 @@ public class QuickSettingsController { mFeatureFlags = featureFlags; mInteractionJankMonitor = interactionJankMonitor; - mShadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged); mLockscreenShadeTransitionController.addCallback(new LockscreenShadeTransitionCallback()); } @@ -878,8 +877,9 @@ public class QuickSettingsController { mCollapsedOnDown = collapsedOnDown; } - void setShadeExpandedHeight(float shadeExpandedHeight) { - mShadeExpandedHeight = shadeExpandedHeight; + void setShadeExpansion(float expandedHeight, float expandedFraction) { + mShadeExpandedHeight = expandedHeight; + mShadeExpandedFraction = expandedFraction; } @VisibleForTesting @@ -1749,11 +1749,6 @@ public class QuickSettingsController { return false; } - @VisibleForTesting - void onPanelExpansionChanged(ShadeExpansionChangeEvent event) { - mShadeExpandedFraction = event.getFraction(); - } - /** * Animate QS closing by flinging it. * If QS is expanded, it will collapse into QQS and stop. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java index 01e042bf5e15..c920e1ec6604 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java @@ -657,25 +657,6 @@ public final class KeyboardShortcutListSearch { R.string.input_switch_input_language_previous), KeyEvent.KEYCODE_SPACE, KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON), - null))), - /* Access emoji: Meta + . */ - new ShortcutMultiMappingInfo( - context.getString(R.string.input_access_emoji), - null, - Arrays.asList( - new ShortcutKeyGroup(new KeyboardShortcutInfo( - context.getString(R.string.input_access_emoji), - KeyEvent.KEYCODE_PERIOD, - KeyEvent.META_META_ON), - null))), - /* Access voice typing: Meta + V */ - new ShortcutMultiMappingInfo( - context.getString(R.string.input_access_voice_typing), - null, - Arrays.asList( - new ShortcutKeyGroup(new KeyboardShortcutInfo( - context.getString(R.string.input_access_voice_typing), - KeyEvent.KEYCODE_V, KeyEvent.META_META_ON), null))) ); return new KeyboardShortcutMultiMappingGroup( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 51c5183ffee9..cac4251bce63 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; - import static com.android.systemui.DejankUtils.whitelistIpcs; import android.app.KeyguardManager; @@ -145,7 +144,10 @@ public class NotificationLockscreenUserManagerImpl implements break; case Intent.ACTION_USER_UNLOCKED: // Start the overview connection to the launcher service - mOverviewProxyServiceLazy.get().startConnectionToCurrentUser(); + // Connect if user hasn't connected yet + if (mOverviewProxyServiceLazy.get().getProxy() == null) { + mOverviewProxyServiceLazy.get().startConnectionToCurrentUser(); + } break; case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION: final IntentSender intentSender = intent.getParcelableExtra( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index b9ac918d50da..79d01b4a73fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -56,6 +56,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController.StateList import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.policy.CallbackController; +import com.android.systemui.util.Compile; import java.io.PrintWriter; import java.util.ArrayList; @@ -299,7 +300,7 @@ public class StatusBarStateControllerImpl implements @Override public boolean setIsDreaming(boolean isDreaming) { - if (Log.isLoggable(TAG, Log.DEBUG)) { + if (Log.isLoggable(TAG, Log.DEBUG) || Compile.IS_DEBUG) { Log.d(TAG, "setIsDreaming:" + isDreaming); } if (mIsDreaming == isDreaming) { @@ -321,6 +322,11 @@ public class StatusBarStateControllerImpl implements } @Override + public boolean isDreaming() { + return mIsDreaming; + } + + @Override public void setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated) { if (mDarkAnimator != null && mDarkAnimator.isRunning()) { if (animated && mDozeAmountTarget == dozeAmount) { @@ -580,6 +586,7 @@ public class StatusBarStateControllerImpl implements pw.println(" mLeaveOpenOnKeyguardHide=" + mLeaveOpenOnKeyguardHide); pw.println(" mKeyguardRequested=" + mKeyguardRequested); pw.println(" mIsDozing=" + mIsDozing); + pw.println(" mIsDreaming=" + mIsDreaming); pw.println(" mListeners{" + mListeners.size() + "}="); for (RankedListener rl : mListeners) { pw.println(" " + rl.mListener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index 274377f5b0dd..6f4eed3c1612 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -28,12 +28,9 @@ import android.database.ContentObserver; import android.hardware.display.AmbientDisplayConfiguration; import android.os.Handler; import android.os.PowerManager; -import android.os.RemoteException; import android.os.SystemProperties; import android.provider.Settings; -import android.service.dreams.IDreamManager; import android.service.notification.StatusBarNotification; -import android.util.Log; import androidx.annotation.NonNull; @@ -70,7 +67,6 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter private final KeyguardStateController mKeyguardStateController; private final ContentResolver mContentResolver; private final PowerManager mPowerManager; - private final IDreamManager mDreamManager; private final AmbientDisplayConfiguration mAmbientDisplayConfiguration; private final BatteryController mBatteryController; private final HeadsUpManager mHeadsUpManager; @@ -112,7 +108,6 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter public NotificationInterruptStateProviderImpl( ContentResolver contentResolver, PowerManager powerManager, - IDreamManager dreamManager, AmbientDisplayConfiguration ambientDisplayConfiguration, BatteryController batteryController, StatusBarStateController statusBarStateController, @@ -126,7 +121,6 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter UserTracker userTracker) { mContentResolver = contentResolver; mPowerManager = powerManager; - mDreamManager = dreamManager; mBatteryController = batteryController; mAmbientDisplayConfiguration = ambientDisplayConfiguration; mStatusBarStateController = statusBarStateController; @@ -287,7 +281,9 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } // If the device is currently dreaming, then launch the FullScreenIntent - if (isDreaming()) { + // We avoid using IDreamManager#isDreaming here as that method will return false during + // the dream's wake-up phase. + if (mStatusBarStateController.isDreaming()) { return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_DEVICE_IS_DREAMING, suppressedByDND); } @@ -365,16 +361,6 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } } } - - private boolean isDreaming() { - try { - return mDreamManager.isDreaming(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to query dream manager.", e); - return false; - } - } - private boolean shouldHeadsUpWhenAwake(NotificationEntry entry, boolean log) { StatusBarNotification sbn = entry.getSbn(); @@ -424,7 +410,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter return false; } - boolean inUse = mPowerManager.isScreenOn() && !isDreaming(); + boolean inUse = mPowerManager.isScreenOn() && !mStatusBarStateController.isDreaming(); if (!inUse) { if (log) mLogger.logNoHeadsUpNotInUse(entry); 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 3b609c15b4dc..10757ae4ad99 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 @@ -392,6 +392,38 @@ class ControlsUiControllerImplTest : SysuiTestCase() { verify(fakeDialogController.dialog).cancel() } + @Test + fun testOnRotationWithPanelUpdateBoundsCalled() { + mockLayoutInflater() + val packageName = "pkg" + `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName)) + val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls")) + val serviceInfo = setUpPanel(panel) + + underTest.show(parent, {}, context) + + val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>() + + verify(controlsListingController).addCallback(capture(captor)) + captor.value.onServicesUpdated(listOf(serviceInfo)) + FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor) + + val taskViewConsumerCaptor = argumentCaptor<Consumer<TaskView>>() + verify(taskViewFactory).create(eq(context), eq(uiExecutor), capture(taskViewConsumerCaptor)) + + val taskView: TaskView = mock { + `when`(this.post(any())).thenAnswer { + uiExecutor.execute(it.arguments[0] as Runnable) + true + } + } + + taskViewConsumerCaptor.value.accept(taskView) + + underTest.onOrientationChange() + verify(taskView).onLocationChanged() + } + private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo { val activity = ComponentName(context, "activity") preferredPanelRepository.setSelectedComponent( diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt index de04ef810dd0..9df7992f979f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt @@ -152,4 +152,12 @@ class PanelTaskViewControllerTest : SysuiTestCase() { listenerCaptor.value.onTaskRemovalStarted(0) verify(taskView).release() } + + @Test + fun testOnRefreshBounds() { + underTest.launchTaskView() + + underTest.refreshBounds() + verify(taskView).onLocationChanged() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 0dc2d1700f15..bdb0e7ed8d9d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -37,6 +37,9 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel +import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy +import com.android.systemui.multishade.data.repository.MultiShadeRepository +import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.NotificationInsetsController @@ -50,8 +53,12 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarViewController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.util.mockito.any +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -65,10 +72,12 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidTestingRunner::class) @RunWithLooper(setAsMainLooper = true) class NotificationShadeWindowViewControllerTest : SysuiTestCase() { + @Mock private lateinit var view: NotificationShadeWindowView @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController @Mock private lateinit var centralSurfaces: CentralSurfaces @@ -102,6 +111,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { private lateinit var underTest: NotificationShadeWindowViewController + private lateinit var testScope: TestScope + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -115,8 +126,12 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition) .thenReturn(emptyFlow<TransitionStep>()) - val featureFlags = FakeFeatureFlags(); + val featureFlags = FakeFeatureFlags() featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false) + featureFlags.set(Flags.DUAL_SHADE, false) + + val inputProxy = MultiShadeInputProxy() + testScope = TestScope() underTest = NotificationShadeWindowViewController( lockscreenShadeTransitionController, @@ -144,6 +159,18 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { keyguardTransitionInteractor, primaryBouncerToGoneTransitionViewModel, featureFlags, + { + MultiShadeInteractor( + applicationScope = testScope.backgroundScope, + repository = + MultiShadeRepository( + applicationContext = context, + inputProxy = inputProxy, + ), + inputProxy = inputProxy, + ) + }, + FakeSystemClock(), ) underTest.setupExpandedStatusBar() @@ -156,147 +183,162 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { // tests need to be added to test the rest of handleDispatchTouchEvent. @Test - fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() { - underTest.setStatusBarViewController(null) + fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() = + testScope.runTest { + underTest.setStatusBarViewController(null) - val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) + val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) - assertThat(returnVal).isFalse() - } + assertThat(returnVal).isFalse() + } @Test - fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() { - underTest.setStatusBarViewController(phoneStatusBarViewController) - val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0) - whenever(phoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true) + fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() = + testScope.runTest { + underTest.setStatusBarViewController(phoneStatusBarViewController) + val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0) + whenever(phoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true) - val returnVal = interactionEventHandler.handleDispatchTouchEvent(ev) + val returnVal = interactionEventHandler.handleDispatchTouchEvent(ev) - verify(phoneStatusBarViewController).sendTouchToView(ev) - assertThat(returnVal).isTrue() - } + verify(phoneStatusBarViewController).sendTouchToView(ev) + assertThat(returnVal).isTrue() + } @Test - fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() { - underTest.setStatusBarViewController(phoneStatusBarViewController) - val downEvBelow = - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0) - interactionEventHandler.handleDispatchTouchEvent(downEvBelow) + fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() = + testScope.runTest { + underTest.setStatusBarViewController(phoneStatusBarViewController) + val downEvBelow = + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0) + interactionEventHandler.handleDispatchTouchEvent(downEvBelow) - val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0) - whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true) + val nextEvent = + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0) + whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true) - val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent) + val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent) - verify(phoneStatusBarViewController).sendTouchToView(nextEvent) - assertThat(returnVal).isTrue() - } + verify(phoneStatusBarViewController).sendTouchToView(nextEvent) + assertThat(returnVal).isTrue() + } @Test - fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() { - underTest.setStatusBarViewController(phoneStatusBarViewController) - whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) - whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) - whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(true) - whenever(phoneStatusBarViewController.sendTouchToView(downEv)).thenReturn(true) - - val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) - - verify(phoneStatusBarViewController).sendTouchToView(downEv) - assertThat(returnVal).isTrue() - } + fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() = + testScope.runTest { + underTest.setStatusBarViewController(phoneStatusBarViewController) + whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) + whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) + whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) + .thenReturn(true) + whenever(phoneStatusBarViewController.sendTouchToView(DOWN_EVENT)).thenReturn(true) + + val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) + + verify(phoneStatusBarViewController).sendTouchToView(DOWN_EVENT) + assertThat(returnVal).isTrue() + } @Test - fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() { - underTest.setStatusBarViewController(phoneStatusBarViewController) - whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) - whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(true) - // Item we're testing - whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(false) - - val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) - - verify(phoneStatusBarViewController, never()).sendTouchToView(downEv) - assertThat(returnVal).isNull() - } + fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() = + testScope.runTest { + underTest.setStatusBarViewController(phoneStatusBarViewController) + whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) + whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) + .thenReturn(true) + // Item we're testing + whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(false) + + val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) + + verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT) + assertThat(returnVal).isNull() + } @Test - fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() { - underTest.setStatusBarViewController(phoneStatusBarViewController) - whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) - whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) - // Item we're testing - whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(false) - - val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) - - verify(phoneStatusBarViewController, never()).sendTouchToView(downEv) - assertThat(returnVal).isNull() - } + fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() = + testScope.runTest { + underTest.setStatusBarViewController(phoneStatusBarViewController) + whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) + whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) + // Item we're testing + whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) + .thenReturn(false) + + val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) + + verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT) + assertThat(returnVal).isNull() + } @Test - fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() { - underTest.setStatusBarViewController(phoneStatusBarViewController) - whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) - whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(true) - // Item we're testing - whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(false) - - val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) - - verify(phoneStatusBarViewController, never()).sendTouchToView(downEv) - assertThat(returnVal).isTrue() - } + fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() = + testScope.runTest { + underTest.setStatusBarViewController(phoneStatusBarViewController) + whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) + whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) + .thenReturn(true) + // Item we're testing + whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(false) + + val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) + + verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT) + assertThat(returnVal).isTrue() + } @Test - fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() { - underTest.setStatusBarViewController(phoneStatusBarViewController) - whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) - whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) - whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(true) + fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() = + testScope.runTest { + underTest.setStatusBarViewController(phoneStatusBarViewController) + whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) + whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) + whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) + .thenReturn(true) - // Down event first - interactionEventHandler.handleDispatchTouchEvent(downEv) + // Down event first + interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) - // Then another event - val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) - whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true) + // Then another event + val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) + whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true) - val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent) + val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent) - verify(phoneStatusBarViewController).sendTouchToView(nextEvent) - assertThat(returnVal).isTrue() - } + verify(phoneStatusBarViewController).sendTouchToView(nextEvent) + assertThat(returnVal).isTrue() + } @Test - fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() { - // Down event within udfpsOverlay bounds while alternateBouncer is showing - whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(downEv)).thenReturn(false) - whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true) - - // Then touch should not be intercepted - val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(downEv) - assertThat(shouldIntercept).isFalse() - } + fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() = + testScope.runTest { + // Down event within udfpsOverlay bounds while alternateBouncer is showing + whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(DOWN_EVENT)) + .thenReturn(false) + whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true) + + // Then touch should not be intercepted + val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT) + assertThat(shouldIntercept).isFalse() + } @Test - fun testGetBouncerContainer() { - Mockito.clearInvocations(view) - underTest.bouncerContainer - verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container) - } + fun testGetBouncerContainer() = + testScope.runTest { + Mockito.clearInvocations(view) + underTest.bouncerContainer + verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container) + } @Test - fun testGetKeyguardMessageArea() { - underTest.keyguardMessageArea - verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area) + fun testGetKeyguardMessageArea() = + testScope.runTest { + underTest.keyguardMessageArea + verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area) + } + + companion object { + private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + private const val VIEW_BOTTOM = 100 } } - -private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) -private const val VIEW_BOTTOM = 100 diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java deleted file mode 100644 index 2797440ce1f1..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright (C) 2017 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; - -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import static kotlinx.coroutines.flow.FlowKt.emptyFlow; - -import android.os.SystemClock; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.view.MotionEvent; -import android.view.ViewGroup; - -import androidx.test.filters.SmallTest; - -import com.android.keyguard.KeyguardSecurityContainerController; -import com.android.keyguard.LockIconViewController; -import com.android.keyguard.dagger.KeyguardBouncerComponent; -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; -import com.android.systemui.classifier.FalsingCollectorFake; -import com.android.systemui.dock.DockManager; -import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.Flags; -import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; -import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; -import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; -import com.android.systemui.statusbar.DragDownHelper; -import com.android.systemui.statusbar.LockscreenShadeTransitionController; -import com.android.systemui.statusbar.NotificationInsetsController; -import com.android.systemui.statusbar.NotificationShadeDepthController; -import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.SysuiStatusBarStateController; -import com.android.systemui.statusbar.notification.stack.AmbientState; -import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; -import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; -import com.android.systemui.statusbar.phone.CentralSurfaces; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.window.StatusBarWindowStateController; -import com.android.systemui.tuner.TunerService; - -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; - -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper(setAsMainLooper = true) -@SmallTest -public class NotificationShadeWindowViewTest extends SysuiTestCase { - - private NotificationShadeWindowView mView; - private NotificationShadeWindowViewController mController; - - @Mock private TunerService mTunerService; - @Mock private DragDownHelper mDragDownHelper; - @Mock private SysuiStatusBarStateController mStatusBarStateController; - @Mock private ShadeController mShadeController; - @Mock private CentralSurfaces mCentralSurfaces; - @Mock private DockManager mDockManager; - @Mock private NotificationPanelViewController mNotificationPanelViewController; - @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout; - @Mock private NotificationShadeDepthController mNotificationShadeDepthController; - @Mock private NotificationShadeWindowController mNotificationShadeWindowController; - @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; - @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - @Mock private StatusBarWindowStateController mStatusBarWindowStateController; - @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController; - @Mock private LockIconViewController mLockIconViewController; - @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; - @Mock private AmbientState mAmbientState; - @Mock private PulsingGestureListener mPulsingGestureListener; - @Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel; - @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory; - @Mock private KeyguardBouncerComponent mKeyguardBouncerComponent; - @Mock private KeyguardSecurityContainerController mKeyguardSecurityContainerController; - @Mock private NotificationInsetsController mNotificationInsetsController; - @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; - @Mock private UdfpsOverlayInteractor mUdfpsOverlayInteractor; - @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; - @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; - - @Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler> - mInteractionEventHandlerCaptor; - private NotificationShadeWindowView.InteractionEventHandler mInteractionEventHandler; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mView = spy(new NotificationShadeWindowView(getContext(), null)); - when(mView.findViewById(R.id.notification_stack_scroller)) - .thenReturn(mNotificationStackScrollLayout); - - when(mView.findViewById(R.id.keyguard_bouncer_container)).thenReturn(mock(ViewGroup.class)); - when(mKeyguardBouncerComponentFactory.create(any(ViewGroup.class))).thenReturn( - mKeyguardBouncerComponent); - when(mKeyguardBouncerComponent.getSecurityContainerController()).thenReturn( - mKeyguardSecurityContainerController); - - when(mStatusBarStateController.isDozing()).thenReturn(false); - mDependency.injectTestDependency(ShadeController.class, mShadeController); - - when(mDockManager.isDocked()).thenReturn(false); - - when(mKeyguardTransitionInteractor.getLockscreenToDreamingTransition()) - .thenReturn(emptyFlow()); - - FakeFeatureFlags featureFlags = new FakeFeatureFlags(); - featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false); - mController = new NotificationShadeWindowViewController( - mLockscreenShadeTransitionController, - new FalsingCollectorFake(), - mStatusBarStateController, - mDockManager, - mNotificationShadeDepthController, - mView, - mNotificationPanelViewController, - new ShadeExpansionStateManager(), - mNotificationStackScrollLayoutController, - mStatusBarKeyguardViewManager, - mStatusBarWindowStateController, - mLockIconViewController, - mCentralSurfaces, - mNotificationShadeWindowController, - mKeyguardUnlockAnimationController, - mNotificationInsetsController, - mAmbientState, - mPulsingGestureListener, - mKeyguardBouncerViewModel, - mKeyguardBouncerComponentFactory, - mAlternateBouncerInteractor, - mUdfpsOverlayInteractor, - mKeyguardTransitionInteractor, - mPrimaryBouncerToGoneTransitionViewModel, - featureFlags - ); - mController.setupExpandedStatusBar(); - mController.setDragDownHelper(mDragDownHelper); - } - - @Test - public void testDragDownHelperCalledWhenDraggingDown() { - when(mDragDownHelper.isDraggingDown()).thenReturn(true); - long now = SystemClock.elapsedRealtime(); - MotionEvent ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0 /* x */, 0 /* y */, - 0 /* meta */); - mView.onTouchEvent(ev); - verify(mDragDownHelper).onTouchEvent(ev); - ev.recycle(); - } - - @Test - public void testInterceptTouchWhenShowingAltAuth() { - captureInteractionEventHandler(); - - // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept - when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - when(mUdfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(any())).thenReturn(true); - when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false); - - // THEN we should intercept touch - assertTrue(mInteractionEventHandler.shouldInterceptTouchEvent(mock(MotionEvent.class))); - } - - @Test - public void testNoInterceptTouch() { - captureInteractionEventHandler(); - - // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept - when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); - when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false); - - // THEN we shouldn't intercept touch - assertFalse(mInteractionEventHandler.shouldInterceptTouchEvent(mock(MotionEvent.class))); - } - - @Test - public void testHandleTouchEventWhenShowingAltAuth() { - captureInteractionEventHandler(); - - // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept - when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false); - - // THEN we should handle the touch - assertTrue(mInteractionEventHandler.handleTouchEvent(mock(MotionEvent.class))); - } - - private void captureInteractionEventHandler() { - verify(mView).setInteractionEventHandler(mInteractionEventHandlerCaptor.capture()); - mInteractionEventHandler = mInteractionEventHandlerCaptor.getValue(); - - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt new file mode 100644 index 000000000000..5d0f408a0522 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2017 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 + +import android.os.SystemClock +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.MotionEvent +import android.widget.FrameLayout +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardSecurityContainerController +import com.android.keyguard.LockIconViewController +import com.android.keyguard.dagger.KeyguardBouncerComponent +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor +import com.android.systemui.classifier.FalsingCollectorFake +import com.android.systemui.dock.DockManager +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.KeyguardUnlockAnimationController +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel +import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy +import com.android.systemui.multishade.data.repository.MultiShadeRepository +import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor +import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler +import com.android.systemui.statusbar.DragDownHelper +import com.android.systemui.statusbar.LockscreenShadeTransitionController +import com.android.systemui.statusbar.NotificationInsetsController +import com.android.systemui.statusbar.NotificationShadeDepthController +import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.SysuiStatusBarStateController +import com.android.systemui.statusbar.notification.stack.AmbientState +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.phone.CentralSurfaces +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import com.android.systemui.statusbar.window.StatusBarWindowStateController +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 com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +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.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidTestingRunner::class) +@RunWithLooper(setAsMainLooper = true) +@SmallTest +class NotificationShadeWindowViewTest : SysuiTestCase() { + + @Mock private lateinit var dragDownHelper: DragDownHelper + @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController + @Mock private lateinit var shadeController: ShadeController + @Mock private lateinit var centralSurfaces: CentralSurfaces + @Mock private lateinit var dockManager: DockManager + @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController + @Mock private lateinit var notificationStackScrollLayout: NotificationStackScrollLayout + @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController + @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController + @Mock + private lateinit var notificationStackScrollLayoutController: + NotificationStackScrollLayoutController + @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager + @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController + @Mock + private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController + @Mock private lateinit var lockIconViewController: LockIconViewController + @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController + @Mock private lateinit var ambientState: AmbientState + @Mock private lateinit var pulsingGestureListener: PulsingGestureListener + @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel + @Mock private lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory + @Mock private lateinit var keyguardBouncerComponent: KeyguardBouncerComponent + @Mock + private lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController + @Mock private lateinit var notificationInsetsController: NotificationInsetsController + @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor + @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor + @Mock + private lateinit var primaryBouncerToGoneTransitionViewModel: + PrimaryBouncerToGoneTransitionViewModel + @Captor + private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler> + @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor + + private lateinit var underTest: NotificationShadeWindowView + private lateinit var controller: NotificationShadeWindowViewController + private lateinit var interactionEventHandler: InteractionEventHandler + private lateinit var testScope: TestScope + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + underTest = spy(NotificationShadeWindowView(context, null)) + whenever( + underTest.findViewById<NotificationStackScrollLayout>( + R.id.notification_stack_scroller + ) + ) + .thenReturn(notificationStackScrollLayout) + whenever(underTest.findViewById<FrameLayout>(R.id.keyguard_bouncer_container)) + .thenReturn(mock()) + whenever(keyguardBouncerComponentFactory.create(any())).thenReturn(keyguardBouncerComponent) + whenever(keyguardBouncerComponent.securityContainerController) + .thenReturn(keyguardSecurityContainerController) + whenever(statusBarStateController.isDozing).thenReturn(false) + mDependency.injectTestDependency(ShadeController::class.java, shadeController) + whenever(dockManager.isDocked).thenReturn(false) + whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition) + .thenReturn(emptyFlow()) + + val featureFlags = FakeFeatureFlags() + featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false) + featureFlags.set(Flags.DUAL_SHADE, false) + val inputProxy = MultiShadeInputProxy() + testScope = TestScope() + controller = + NotificationShadeWindowViewController( + lockscreenShadeTransitionController, + FalsingCollectorFake(), + statusBarStateController, + dockManager, + notificationShadeDepthController, + underTest, + notificationPanelViewController, + ShadeExpansionStateManager(), + notificationStackScrollLayoutController, + statusBarKeyguardViewManager, + statusBarWindowStateController, + lockIconViewController, + centralSurfaces, + notificationShadeWindowController, + keyguardUnlockAnimationController, + notificationInsetsController, + ambientState, + pulsingGestureListener, + keyguardBouncerViewModel, + keyguardBouncerComponentFactory, + alternateBouncerInteractor, + udfpsOverlayInteractor, + keyguardTransitionInteractor, + primaryBouncerToGoneTransitionViewModel, + featureFlags, + { + MultiShadeInteractor( + applicationScope = testScope.backgroundScope, + repository = + MultiShadeRepository( + applicationContext = context, + inputProxy = inputProxy, + ), + inputProxy = inputProxy, + ) + }, + FakeSystemClock(), + ) + + controller.setupExpandedStatusBar() + controller.setDragDownHelper(dragDownHelper) + } + + @Test + fun testDragDownHelperCalledWhenDraggingDown() = + testScope.runTest { + whenever(dragDownHelper.isDraggingDown).thenReturn(true) + val now = SystemClock.elapsedRealtime() + val ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0f, 0f, 0 /* meta */) + underTest.onTouchEvent(ev) + verify(dragDownHelper).onTouchEvent(ev) + ev.recycle() + } + + @Test + fun testInterceptTouchWhenShowingAltAuth() = + testScope.runTest { + captureInteractionEventHandler() + + // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept + whenever(statusBarStateController.isDozing).thenReturn(false) + whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true) + whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(any())).thenReturn(true) + whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false) + + // THEN we should intercept touch + assertThat(interactionEventHandler.shouldInterceptTouchEvent(mock())).isTrue() + } + + @Test + fun testNoInterceptTouch() = + testScope.runTest { + captureInteractionEventHandler() + + // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept + whenever(statusBarStateController.isDozing).thenReturn(false) + whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(false) + whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false) + + // THEN we shouldn't intercept touch + assertThat(interactionEventHandler.shouldInterceptTouchEvent(mock())).isFalse() + } + + @Test + fun testHandleTouchEventWhenShowingAltAuth() = + testScope.runTest { + captureInteractionEventHandler() + + // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept + whenever(statusBarStateController.isDozing).thenReturn(false) + whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true) + whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false) + + // THEN we should handle the touch + assertThat(interactionEventHandler.handleTouchEvent(mock())).isTrue() + } + + private fun captureInteractionEventHandler() { + verify(underTest).setInteractionEventHandler(interactionEventHandlerCaptor.capture()) + interactionEventHandler = interactionEventHandlerCaptor.value + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java index 15b84238dd19..d8ffe39e427d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java @@ -275,9 +275,7 @@ public class QuickSettingsControllerTest extends SysuiTestCase { @Test public void testPanelStaysOpenWhenClosingQs() { - mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1, - /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0); - mQsController.setShadeExpandedHeight(1); + mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1); float shadeExpandedHeight = mQsController.getShadeExpandedHeight(); mQsController.animateCloseQs(false); @@ -289,7 +287,7 @@ public class QuickSettingsControllerTest extends SysuiTestCase { public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() { mQsController.setQs(mQs); - mQsController.setShadeExpandedHeight(1f); + mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1); mQsController.onIntercept( createMotionEvent(0, 0, ACTION_DOWN)); mQsController.onIntercept( @@ -303,7 +301,7 @@ public class QuickSettingsControllerTest extends SysuiTestCase { enableSplitShade(true); mQsController.setQs(mQs); - mQsController.setShadeExpandedHeight(1f); + mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1); mQsController.onIntercept( createMotionEvent(0, 0, ACTION_DOWN)); mQsController.onIntercept( @@ -342,13 +340,8 @@ public class QuickSettingsControllerTest extends SysuiTestCase { public void handleTouch_downActionInQsArea() { mQsController.setQs(mQs); mQsController.setBarState(SHADE); - mQsController.onPanelExpansionChanged( - new ShadeExpansionChangeEvent( - 0.5f, - true, - true, - 0 - )); + mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 0.5f); + MotionEvent event = createMotionEvent(QS_FRAME_WIDTH / 2, QS_FRAME_BOTTOM / 2, ACTION_DOWN); mQsController.handleTouch(event, false, false); @@ -385,7 +378,7 @@ public class QuickSettingsControllerTest extends SysuiTestCase { @Test public void handleTouch_isConflictingExpansionGestureSet() { assertThat(mQsController.isConflictingExpansionGesture()).isFalse(); - mShadeExpansionStateManager.onPanelExpansionChanged(1f, true, false, 0f); + mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1); mQsController.handleTouch(MotionEvent.obtain(0L /* downTime */, 0L /* eventTime */, ACTION_DOWN, 0f /* x */, 0f /* y */, 0 /* metaState */), false, false); @@ -394,7 +387,7 @@ public class QuickSettingsControllerTest extends SysuiTestCase { @Test public void handleTouch_isConflictingExpansionGestureSet_cancel() { - mShadeExpansionStateManager.onPanelExpansionChanged(1f, true, false, 0f); + mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1); mQsController.handleTouch(createMotionEvent(0, 0, ACTION_DOWN), false, false); assertThat(mQsController.isConflictingExpansionGesture()).isTrue(); mQsController.handleTouch(createMotionEvent(0, 0, ACTION_UP), true, true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt index 1fdb3647fcb2..374aae1f2948 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt @@ -27,13 +27,16 @@ import com.android.systemui.plugins.ClockMetadata import com.android.systemui.plugins.ClockProviderPlugin import com.android.systemui.plugins.ClockSettings import com.android.systemui.plugins.PluginListener +import com.android.systemui.plugins.PluginLifecycleManager import com.android.systemui.plugins.PluginManager import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock import junit.framework.Assert.assertEquals import junit.framework.Assert.fail import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestCoroutineScheduler import kotlinx.coroutines.test.TestScope import org.junit.Before import org.junit.Rule @@ -49,6 +52,7 @@ import org.mockito.junit.MockitoJUnit class ClockRegistryTest : SysuiTestCase() { @JvmField @Rule val mockito = MockitoJUnit.rule() + private lateinit var scheduler: TestCoroutineScheduler private lateinit var dispatcher: CoroutineDispatcher private lateinit var scope: TestScope @@ -58,37 +62,38 @@ class ClockRegistryTest : SysuiTestCase() { @Mock private lateinit var mockDefaultClock: ClockController @Mock private lateinit var mockThumbnail: Drawable @Mock private lateinit var mockContentResolver: ContentResolver + @Mock private lateinit var mockPluginLifecycle: PluginLifecycleManager<ClockProviderPlugin> private lateinit var fakeDefaultProvider: FakeClockPlugin private lateinit var pluginListener: PluginListener<ClockProviderPlugin> private lateinit var registry: ClockRegistry companion object { - private fun failFactory(): ClockController { - fail("Unexpected call to createClock") + private fun failFactory(clockId: ClockId): ClockController { + fail("Unexpected call to createClock: $clockId") return null!! } - private fun failThumbnail(): Drawable? { - fail("Unexpected call to getThumbnail") + private fun failThumbnail(clockId: ClockId): Drawable? { + fail("Unexpected call to getThumbnail: $clockId") return null } } private class FakeClockPlugin : ClockProviderPlugin { private val metadata = mutableListOf<ClockMetadata>() - private val createCallbacks = mutableMapOf<ClockId, () -> ClockController>() - private val thumbnailCallbacks = mutableMapOf<ClockId, () -> Drawable?>() + private val createCallbacks = mutableMapOf<ClockId, (ClockId) -> ClockController>() + private val thumbnailCallbacks = mutableMapOf<ClockId, (ClockId) -> Drawable?>() override fun getClocks() = metadata override fun createClock(settings: ClockSettings): ClockController = - createCallbacks[settings.clockId!!]!!() - override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!() + createCallbacks[settings.clockId!!]!!(settings.clockId!!) + override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!(id) fun addClock( id: ClockId, name: String, - create: () -> ClockController = ::failFactory, - getThumbnail: () -> Drawable? = ::failThumbnail + create: (ClockId) -> ClockController = ::failFactory, + getThumbnail: (ClockId) -> Drawable? = ::failThumbnail ): FakeClockPlugin { metadata.add(ClockMetadata(id, name)) createCallbacks[id] = create @@ -99,7 +104,8 @@ class ClockRegistryTest : SysuiTestCase() { @Before fun setUp() { - dispatcher = StandardTestDispatcher() + scheduler = TestCoroutineScheduler() + dispatcher = StandardTestDispatcher(scheduler) scope = TestScope(dispatcher) fakeDefaultProvider = FakeClockPlugin() @@ -116,6 +122,8 @@ class ClockRegistryTest : SysuiTestCase() { isEnabled = true, handleAllUsers = true, defaultClockProvider = fakeDefaultProvider, + keepAllLoaded = true, + subTag = "Test", ) { override fun querySettings() { } override fun applySettings(value: ClockSettings?) { @@ -142,8 +150,8 @@ class ClockRegistryTest : SysuiTestCase() { .addClock("clock_3", "clock 3") .addClock("clock_4", "clock 4") - pluginListener.onPluginConnected(plugin1, mockContext) - pluginListener.onPluginConnected(plugin2, mockContext) + pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle) + pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle) val list = registry.getClocks() assertEquals( list, @@ -165,16 +173,18 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun clockIdConflict_ErrorWithoutCrash() { + val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>() val plugin1 = FakeClockPlugin() .addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail }) .addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail }) + val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>() val plugin2 = FakeClockPlugin() .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") - pluginListener.onPluginConnected(plugin1, mockContext) - pluginListener.onPluginConnected(plugin2, mockContext) + pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle1) + pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2) val list = registry.getClocks() assertEquals( list, @@ -202,8 +212,8 @@ class ClockRegistryTest : SysuiTestCase() { .addClock("clock_4", "clock 4") registry.applySettings(ClockSettings("clock_3", null)) - pluginListener.onPluginConnected(plugin1, mockContext) - pluginListener.onPluginConnected(plugin2, mockContext) + pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle) + pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle) val clock = registry.createCurrentClock() assertEquals(mockClock, clock) @@ -220,9 +230,9 @@ class ClockRegistryTest : SysuiTestCase() { .addClock("clock_4", "clock 4") registry.applySettings(ClockSettings("clock_3", null)) - pluginListener.onPluginConnected(plugin1, mockContext) - pluginListener.onPluginConnected(plugin2, mockContext) - pluginListener.onPluginDisconnected(plugin2) + pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle) + pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle) + pluginListener.onPluginUnloaded(plugin2, mockPluginLifecycle) val clock = registry.createCurrentClock() assertEquals(clock, mockDefaultClock) @@ -230,15 +240,16 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun pluginRemoved_clockAndListChanged() { + val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>() val plugin1 = FakeClockPlugin() .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") + val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>() val plugin2 = FakeClockPlugin() .addClock("clock_3", "clock 3", { mockClock }) .addClock("clock_4", "clock 4") - var changeCallCount = 0 var listChangeCallCount = 0 registry.registerClockChangeListener(object : ClockRegistry.ClockChangeListener { @@ -247,23 +258,38 @@ class ClockRegistryTest : SysuiTestCase() { }) registry.applySettings(ClockSettings("clock_3", null)) - assertEquals(0, changeCallCount) + scheduler.runCurrent() + assertEquals(1, changeCallCount) assertEquals(0, listChangeCallCount) - pluginListener.onPluginConnected(plugin1, mockContext) - assertEquals(0, changeCallCount) + pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle1) + scheduler.runCurrent() + assertEquals(1, changeCallCount) assertEquals(1, listChangeCallCount) - pluginListener.onPluginConnected(plugin2, mockContext) - assertEquals(1, changeCallCount) + pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2) + scheduler.runCurrent() + assertEquals(2, changeCallCount) assertEquals(2, listChangeCallCount) - pluginListener.onPluginDisconnected(plugin1) - assertEquals(1, changeCallCount) + pluginListener.onPluginUnloaded(plugin1, mockPluginLifecycle1) + scheduler.runCurrent() + assertEquals(2, changeCallCount) + assertEquals(2, listChangeCallCount) + + pluginListener.onPluginUnloaded(plugin2, mockPluginLifecycle2) + scheduler.runCurrent() + assertEquals(3, changeCallCount) + assertEquals(2, listChangeCallCount) + + pluginListener.onPluginDetached(mockPluginLifecycle1) + scheduler.runCurrent() + assertEquals(3, changeCallCount) assertEquals(3, listChangeCallCount) - pluginListener.onPluginDisconnected(plugin2) - assertEquals(2, changeCallCount) + pluginListener.onPluginDetached(mockPluginLifecycle2) + scheduler.runCurrent() + assertEquals(3, changeCallCount) assertEquals(4, listChangeCallCount) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java index 05280fa826ed..c39b29fb4435 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java @@ -79,11 +79,11 @@ public class PluginActionManagerTest extends SysuiTestCase { private PluginInstance<TestPlugin> mPluginInstance; private PluginInstance.Factory mPluginInstanceFactory = new PluginInstance.Factory( this.getClass().getClassLoader(), - new PluginInstance.InstanceFactory<>(), new PluginInstance.VersionChecker(), + new PluginInstance.InstanceFactory<>(), new PluginInstance.VersionCheckerImpl(), Collections.emptyList(), false) { @Override public <T extends Plugin> PluginInstance<T> create(Context context, ApplicationInfo appInfo, - ComponentName componentName, Class<T> pluginClass) { + ComponentName componentName, Class<T> pluginClass, PluginListener<T> listener) { return (PluginInstance<T>) mPluginInstance; } }; @@ -128,7 +128,7 @@ public class PluginActionManagerTest extends SysuiTestCase { createPlugin(); // Verify startup lifecycle - verify(mPluginInstance).onCreate(mContext, mMockListener); + verify(mPluginInstance).onCreate(); } @Test @@ -140,7 +140,7 @@ public class PluginActionManagerTest extends SysuiTestCase { mFakeExecutor.runAllReady(); // Verify shutdown lifecycle - verify(mPluginInstance).onDestroy(mMockListener); + verify(mPluginInstance).onDestroy(); } @Test @@ -152,9 +152,9 @@ public class PluginActionManagerTest extends SysuiTestCase { mFakeExecutor.runAllReady(); // Verify the old one was destroyed. - verify(mPluginInstance).onDestroy(mMockListener); + verify(mPluginInstance).onDestroy(); verify(mPluginInstance, Mockito.times(2)) - .onCreate(mContext, mMockListener); + .onCreate(); } @Test @@ -188,7 +188,7 @@ public class PluginActionManagerTest extends SysuiTestCase { mFakeExecutor.runAllReady(); // Verify startup lifecycle - verify(mPluginInstance).onCreate(mContext, mMockListener); + verify(mPluginInstance).onCreate(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java index bb9a1e971fd0..d5e904c636d5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java @@ -16,11 +16,9 @@ package com.android.systemui.shared.plugins; +import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; - -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static junit.framework.Assert.assertNull; import android.content.ComponentName; import android.content.Context; @@ -31,6 +29,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.PluginLifecycleManager; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.annotations.ProvidesInterface; import com.android.systemui.plugins.annotations.Requires; @@ -38,46 +37,64 @@ import com.android.systemui.plugins.annotations.Requires; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import java.lang.ref.WeakReference; import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; @SmallTest @RunWith(AndroidJUnit4.class) public class PluginInstanceTest extends SysuiTestCase { private static final String PRIVILEGED_PACKAGE = "com.android.systemui.plugins"; + private static final ComponentName TEST_PLUGIN_COMPONENT_NAME = + new ComponentName(PRIVILEGED_PACKAGE, TestPluginImpl.class.getName()); - @Mock - private TestPluginImpl mMockPlugin; - @Mock - private PluginListener<TestPlugin> mMockListener; - @Mock + private FakeListener mPluginListener; private VersionInfo mVersionInfo; - ComponentName mTestPluginComponentName = - new ComponentName(PRIVILEGED_PACKAGE, TestPluginImpl.class.getName()); + private VersionInfo.InvalidVersionException mVersionException; + private PluginInstance.VersionChecker mVersionChecker; + + private RefCounter mCounter; private PluginInstance<TestPlugin> mPluginInstance; private PluginInstance.Factory mPluginInstanceFactory; - private ApplicationInfo mAppInfo; - private Context mPluginContext; - @Mock - private PluginInstance.VersionChecker mVersionChecker; + + // Because we're testing memory in this file, we must be careful not to assert the target + // objects, or capture them via mockito if we expect the garbage collector to later free them. + // Both JUnit and Mockito will save references and prevent these objects from being cleaned up. + private WeakReference<TestPluginImpl> mPlugin; + private WeakReference<Context> mPluginContext; @Before public void setup() throws Exception { - MockitoAnnotations.initMocks(this); + mCounter = new RefCounter(); mAppInfo = mContext.getApplicationInfo(); - mAppInfo.packageName = mTestPluginComponentName.getPackageName(); - when(mVersionChecker.checkVersion(any(), any(), any())).thenReturn(mVersionInfo); + mAppInfo.packageName = TEST_PLUGIN_COMPONENT_NAME.getPackageName(); + mPluginListener = new FakeListener(); + mVersionInfo = new VersionInfo(); + mVersionChecker = new PluginInstance.VersionChecker() { + @Override + public <T extends Plugin> VersionInfo checkVersion( + Class<T> instanceClass, + Class<T> pluginClass, + Plugin plugin + ) { + if (mVersionException != null) { + throw mVersionException; + } + return mVersionInfo; + } + }; mPluginInstanceFactory = new PluginInstance.Factory( this.getClass().getClassLoader(), new PluginInstance.InstanceFactory<TestPlugin>() { @Override TestPlugin create(Class cls) { - return mMockPlugin; + TestPluginImpl plugin = new TestPluginImpl(mCounter); + mPlugin = new WeakReference<>(plugin); + return plugin; } }, mVersionChecker, @@ -85,8 +102,9 @@ public class PluginInstanceTest extends SysuiTestCase { false); mPluginInstance = mPluginInstanceFactory.create( - mContext, mAppInfo, mTestPluginComponentName, TestPlugin.class); - mPluginContext = mPluginInstance.getPluginContext(); + mContext, mAppInfo, TEST_PLUGIN_COMPONENT_NAME, + TestPlugin.class, mPluginListener); + mPluginContext = new WeakReference<>(mPluginInstance.getPluginContext()); } @Test @@ -96,29 +114,51 @@ public class PluginInstanceTest extends SysuiTestCase { @Test(expected = VersionInfo.InvalidVersionException.class) public void testIncorrectVersion() throws Exception { - ComponentName wrongVersionTestPluginComponentName = new ComponentName(PRIVILEGED_PACKAGE, TestPlugin.class.getName()); - when(mVersionChecker.checkVersion(any(), any(), any())).thenThrow( - new VersionInfo.InvalidVersionException("test", true)); + mVersionException = new VersionInfo.InvalidVersionException("test", true); mPluginInstanceFactory.create( - mContext, mAppInfo, wrongVersionTestPluginComponentName, TestPlugin.class); + mContext, mAppInfo, wrongVersionTestPluginComponentName, + TestPlugin.class, mPluginListener); } @Test public void testOnCreate() { - mPluginInstance.onCreate(mContext, mMockListener); - verify(mMockPlugin).onCreate(mContext, mPluginContext); - verify(mMockListener).onPluginConnected(mMockPlugin, mPluginContext); + mPluginInstance.onCreate(); + assertEquals(1, mPluginListener.mAttachedCount); + assertEquals(1, mPluginListener.mLoadCount); + assertEquals(mPlugin.get(), mPluginInstance.getPlugin()); + assertInstances(1, 1); } @Test public void testOnDestroy() { - mPluginInstance.onDestroy(mMockListener); - verify(mMockListener).onPluginDisconnected(mMockPlugin); - verify(mMockPlugin).onDestroy(); + mPluginInstance.onDestroy(); + assertEquals(1, mPluginListener.mDetachedCount); + assertEquals(1, mPluginListener.mUnloadCount); + assertNull(mPluginInstance.getPlugin()); + assertInstances(0, -1); // Destroyed but never created + } + + @Test + public void testOnRepeatedlyLoadUnload_PluginFreed() { + mPluginInstance.onCreate(); + mPluginInstance.loadPlugin(); + assertInstances(1, 1); + + mPluginInstance.unloadPlugin(); + assertNull(mPluginInstance.getPlugin()); + assertInstances(0, 0); + + mPluginInstance.loadPlugin(); + assertInstances(1, 1); + + mPluginInstance.unloadPlugin(); + mPluginInstance.onDestroy(); + assertNull(mPluginInstance.getPlugin()); + assertInstances(0, 0); } // This target class doesn't matter, it just needs to have a Requires to hit the flow where @@ -129,10 +169,103 @@ public class PluginInstanceTest extends SysuiTestCase { String ACTION = "testAction"; } + public void assertInstances(Integer allocated, Integer created) { + // Run the garbage collector to finalize and deallocate outstanding + // instances. Since the GC doesn't always appear to want to run + // completely when we ask, we ask it 10 times in a short loop. + for (int i = 0; i < 10; i++) { + System.runFinalization(); + System.gc(); + } + + mCounter.assertInstances(allocated, created); + } + + public static class RefCounter { + public final AtomicInteger mAllocatedInstances = new AtomicInteger(); + public final AtomicInteger mCreatedInstances = new AtomicInteger(); + + public void assertInstances(Integer allocated, Integer created) { + if (allocated != null) { + assertEquals(allocated.intValue(), mAllocatedInstances.get()); + } + if (created != null) { + assertEquals(created.intValue(), mCreatedInstances.get()); + } + } + } + @Requires(target = TestPlugin.class, version = TestPlugin.VERSION) public static class TestPluginImpl implements TestPlugin { + public final RefCounter mCounter; + public TestPluginImpl(RefCounter counter) { + mCounter = counter; + mCounter.mAllocatedInstances.getAndIncrement(); + } + + @Override + public void finalize() { + mCounter.mAllocatedInstances.getAndDecrement(); + } + @Override public void onCreate(Context sysuiContext, Context pluginContext) { + mCounter.mCreatedInstances.getAndIncrement(); + } + + @Override + public void onDestroy() { + mCounter.mCreatedInstances.getAndDecrement(); + } + } + + public class FakeListener implements PluginListener<TestPlugin> { + public int mAttachedCount = 0; + public int mDetachedCount = 0; + public int mLoadCount = 0; + public int mUnloadCount = 0; + + @Override + public void onPluginAttached(PluginLifecycleManager<TestPlugin> manager) { + mAttachedCount++; + assertEquals(PluginInstanceTest.this.mPluginInstance, manager); + } + + @Override + public void onPluginDetached(PluginLifecycleManager<TestPlugin> manager) { + mDetachedCount++; + assertEquals(PluginInstanceTest.this.mPluginInstance, manager); + } + + @Override + public void onPluginLoaded( + TestPlugin plugin, + Context pluginContext, + PluginLifecycleManager<TestPlugin> manager + ) { + mLoadCount++; + TestPlugin expectedPlugin = PluginInstanceTest.this.mPlugin.get(); + if (expectedPlugin != null) { + assertEquals(expectedPlugin, plugin); + } + Context expectedContext = PluginInstanceTest.this.mPluginContext.get(); + if (expectedContext != null) { + assertEquals(expectedContext, pluginContext); + } + assertEquals(PluginInstanceTest.this.mPluginInstance, manager); + } + + @Override + public void onPluginUnloaded( + TestPlugin plugin, + PluginLifecycleManager<TestPlugin> manager + ) { + mUnloadCount++; + TestPlugin expectedPlugin = PluginInstanceTest.this.mPlugin.get(); + if (expectedPlugin != null) { + assertEquals(expectedPlugin, plugin); + } + assertEquals(PluginInstanceTest.this.mPluginInstance, manager); } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index e6f272b3ad70..3327e42b930f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -167,4 +167,13 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { controller.setIsDreaming(false) verify(listener).onDreamingChanged(false) } + + @Test + fun testSetDreamState_getterReturnsCurrentState() { + controller.setIsDreaming(true) + assertTrue(controller.isDreaming()) + + controller.setIsDreaming(false) + assertFalse(controller.isDreaming()) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java index 8acf507e3452..653b0c707240 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java @@ -54,7 +54,6 @@ import android.hardware.display.AmbientDisplayConfiguration; import android.os.Handler; import android.os.PowerManager; import android.os.RemoteException; -import android.service.dreams.IDreamManager; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; @@ -94,8 +93,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { @Mock PowerManager mPowerManager; @Mock - IDreamManager mDreamManager; - @Mock AmbientDisplayConfiguration mAmbientDisplayConfiguration; @Mock StatusBarStateController mStatusBarStateController; @@ -133,7 +130,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { new NotificationInterruptStateProviderImpl( mContext.getContentResolver(), mPowerManager, - mDreamManager, mAmbientDisplayConfiguration, mBatteryController, mStatusBarStateController, @@ -157,7 +153,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { when(mHeadsUpManager.isSnoozed(any())).thenReturn(false); when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); when(mPowerManager.isScreenOn()).thenReturn(true); } @@ -359,7 +355,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { // Also not in use if screen is on but we're showing screen saver / "dreaming" when(mPowerManager.isDeviceIdleMode()).thenReturn(true); - when(mDreamManager.isDreaming()).thenReturn(true); + when(mStatusBarStateController.isDreaming()).thenReturn(true); assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse(); } @@ -539,7 +535,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { public void testShouldNotFullScreen_notPendingIntent() throws RemoteException { NotificationEntry entry = createNotification(IMPORTANCE_HIGH); when(mPowerManager.isInteractive()).thenReturn(true); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); when(mStatusBarStateController.getState()).thenReturn(SHADE); assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry)) @@ -558,7 +554,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { .setSuppressedVisualEffects(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) .build(); when(mPowerManager.isInteractive()).thenReturn(false); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); when(mStatusBarStateController.getState()).thenReturn(SHADE); assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry)) @@ -577,7 +573,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { .setSuppressedVisualEffects(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) .build(); when(mPowerManager.isInteractive()).thenReturn(false); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); when(mStatusBarStateController.getState()).thenReturn(SHADE); assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry)) @@ -599,7 +595,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { public void testShouldNotFullScreen_notHighImportance() throws RemoteException { NotificationEntry entry = createFsiNotification(IMPORTANCE_DEFAULT, /* silenced */ false); when(mPowerManager.isInteractive()).thenReturn(true); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); when(mStatusBarStateController.getState()).thenReturn(SHADE); assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry)) @@ -621,7 +617,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { public void testShouldNotFullScreen_isGroupAlertSilenced() throws RemoteException { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ true); when(mPowerManager.isInteractive()).thenReturn(false); - when(mDreamManager.isDreaming()).thenReturn(true); + when(mStatusBarStateController.isDreaming()).thenReturn(true); when(mStatusBarStateController.getState()).thenReturn(KEYGUARD); assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry)) @@ -651,7 +647,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { public void testShouldFullScreen_notInteractive() throws RemoteException { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); when(mPowerManager.isInteractive()).thenReturn(false); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); when(mStatusBarStateController.getState()).thenReturn(SHADE); assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry)) @@ -673,7 +669,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { public void testShouldFullScreen_isDreaming() throws RemoteException { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); when(mPowerManager.isInteractive()).thenReturn(true); - when(mDreamManager.isDreaming()).thenReturn(true); + when(mStatusBarStateController.isDreaming()).thenReturn(true); when(mStatusBarStateController.getState()).thenReturn(SHADE); assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry)) @@ -695,7 +691,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { public void testShouldFullScreen_onKeyguard() throws RemoteException { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); when(mPowerManager.isInteractive()).thenReturn(true); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); when(mStatusBarStateController.getState()).thenReturn(KEYGUARD); assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry)) @@ -718,7 +714,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); when(mPowerManager.isInteractive()).thenReturn(true); when(mPowerManager.isScreenOn()).thenReturn(true); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); when(mStatusBarStateController.getState()).thenReturn(SHADE); assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry)) @@ -735,7 +731,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); when(mPowerManager.isInteractive()).thenReturn(true); when(mPowerManager.isScreenOn()).thenReturn(true); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); when(mStatusBarStateController.getState()).thenReturn(SHADE); when(mHeadsUpManager.isSnoozed("a")).thenReturn(true); @@ -754,7 +750,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); when(mPowerManager.isInteractive()).thenReturn(true); when(mPowerManager.isScreenOn()).thenReturn(true); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); when(mStatusBarStateController.getState()).thenReturn(SHADE); when(mHeadsUpManager.isSnoozed("a")).thenReturn(true); when(mKeyguardStateController.isShowing()).thenReturn(true); @@ -775,7 +771,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); when(mPowerManager.isInteractive()).thenReturn(true); when(mPowerManager.isScreenOn()).thenReturn(true); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); when(mStatusBarStateController.getState()).thenReturn(SHADE); when(mHeadsUpManager.isSnoozed("a")).thenReturn(true); when(mKeyguardStateController.isShowing()).thenReturn(true); @@ -800,7 +796,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); when(mPowerManager.isInteractive()).thenReturn(true); when(mPowerManager.isScreenOn()).thenReturn(true); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); when(mStatusBarStateController.getState()).thenReturn(SHADE_LOCKED); when(mHeadsUpManager.isSnoozed("a")).thenReturn(true); when(mKeyguardStateController.isShowing()).thenReturn(true); @@ -821,7 +817,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); when(mPowerManager.isInteractive()).thenReturn(true); when(mPowerManager.isScreenOn()).thenReturn(true); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); when(mStatusBarStateController.getState()).thenReturn(SHADE_LOCKED); when(mHeadsUpManager.isSnoozed("a")).thenReturn(true); when(mKeyguardStateController.isShowing()).thenReturn(true); @@ -846,7 +842,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); when(mPowerManager.isInteractive()).thenReturn(true); when(mPowerManager.isScreenOn()).thenReturn(true); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); when(mStatusBarStateController.getState()).thenReturn(SHADE); when(mHeadsUpManager.isSnoozed("a")).thenReturn(true); when(mKeyguardStateController.isShowing()).thenReturn(false); @@ -892,7 +888,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); when(mPowerManager.isInteractive()).thenReturn(true); when(mPowerManager.isScreenOn()).thenReturn(true); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); when(mStatusBarStateController.getState()).thenReturn(SHADE); when(mHeadsUpManager.isSnoozed("a")).thenReturn(true); when(mKeyguardStateController.isShowing()).thenReturn(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 031c17fd9af0..7db219719bf0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -347,7 +347,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mNotificationInterruptStateProvider = new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(), mPowerManager, - mDreamManager, mAmbientDisplayConfiguration, mStatusBarStateController, mKeyguardStateController, @@ -730,7 +729,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { public void testShouldHeadsUp_nonSuppressedGroupSummary() throws Exception { when(mPowerManager.isScreenOn()).thenReturn(true); when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); Notification n = new Notification.Builder(getContext(), "a") .setGroup("a") @@ -753,7 +752,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { public void testShouldHeadsUp_suppressedGroupSummary() throws Exception { when(mPowerManager.isScreenOn()).thenReturn(true); when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); Notification n = new Notification.Builder(getContext(), "a") .setGroup("a") @@ -776,7 +775,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { public void testShouldHeadsUp_suppressedHeadsUp() throws Exception { when(mPowerManager.isScreenOn()).thenReturn(true); when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); Notification n = new Notification.Builder(getContext(), "a").build(); @@ -797,7 +796,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { public void testShouldHeadsUp_noSuppressedHeadsUp() throws Exception { when(mPowerManager.isScreenOn()).thenReturn(true); when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false); - when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); Notification n = new Notification.Builder(getContext(), "a").build(); @@ -1400,7 +1399,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { TestableNotificationInterruptStateProviderImpl( ContentResolver contentResolver, PowerManager powerManager, - IDreamManager dreamManager, AmbientDisplayConfiguration ambientDisplayConfiguration, StatusBarStateController controller, KeyguardStateController keyguardStateController, @@ -1415,7 +1413,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { super( contentResolver, powerManager, - dreamManager, ambientDisplayConfiguration, batteryController, controller, diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 12e58c97d523..ee4e00baafe6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -343,7 +343,6 @@ public class BubblesTest extends SysuiTestCase { TestableNotificationInterruptStateProviderImpl interruptionStateProvider = new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(), mock(PowerManager.class), - mock(IDreamManager.class), mock(AmbientDisplayConfiguration.class), mock(StatusBarStateController.class), mock(KeyguardStateController.class), @@ -1246,6 +1245,24 @@ public class BubblesTest extends SysuiTestCase { // Show the menu stackView.showManageMenu(true); assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */); + assertTrue(stackView.isManageMenuSettingsVisible()); + } + + @Test + public void testShowManageMenuChangesSysuiState_appBubble() { + mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0); + assertTrue(mBubbleController.hasBubbles()); + + // Expand the stack + BubbleStackView stackView = mBubbleController.getStackView(); + mBubbleData.setExpanded(true); + assertStackExpanded(); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); + + // Show the menu + stackView.showManageMenu(true); + assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */); + assertFalse(stackView.isManageMenuSettingsVisible()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java index ceee0bc466d2..4e14bbf6ac1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java @@ -20,7 +20,6 @@ import android.content.ContentResolver; import android.hardware.display.AmbientDisplayConfiguration; import android.os.Handler; import android.os.PowerManager; -import android.service.dreams.IDreamManager; import com.android.internal.logging.UiEventLogger; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -39,7 +38,6 @@ public class TestableNotificationInterruptStateProviderImpl TestableNotificationInterruptStateProviderImpl( ContentResolver contentResolver, PowerManager powerManager, - IDreamManager dreamManager, AmbientDisplayConfiguration ambientDisplayConfiguration, StatusBarStateController statusBarStateController, KeyguardStateController keyguardStateController, @@ -53,7 +51,6 @@ public class TestableNotificationInterruptStateProviderImpl UserTracker userTracker) { super(contentResolver, powerManager, - dreamManager, ambientDisplayConfiguration, batteryController, statusBarStateController, diff --git a/services/core/java/com/android/server/biometrics/TEST_MAPPING b/services/core/java/com/android/server/biometrics/TEST_MAPPING index 8b80674070a4..daca00b40768 100644 --- a/services/core/java/com/android/server/biometrics/TEST_MAPPING +++ b/services/core/java/com/android/server/biometrics/TEST_MAPPING @@ -6,5 +6,24 @@ { "name": "CtsBiometricsHostTestCases" } - ] -}
\ No newline at end of file + ], + "ironwood-postsubmit": [ + { + "name": "BiometricsE2eTests", + "options": [ + { + "include-annotation": "android.platform.test.annotations.IwTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "include-filter": "android.platform.test.scenario.biometrics" + }, + { + "exclude-annotation": "android.platform.test.annotations.FlakyTest" + } + ] + } + ] +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index dc00ffc9f922..01ffc7e29ac0 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -314,7 +314,8 @@ public class FingerprintService extends SystemService { final FingerprintSensorPropertiesInternal sensorProps = provider.second.getSensorProperties(options.getSensorId()); if (!isKeyguard && !Utils.isSettings(getContext(), opPackageName) - && sensorProps != null && sensorProps.isAnyUdfpsType()) { + && sensorProps != null && (sensorProps.isAnyUdfpsType() + || sensorProps.isAnySidefpsType())) { try { return authenticateWithPrompt(operationId, sensorProps, callingUid, callingUserId, receiver, opPackageName, diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java index a589313a1e92..00af22468fd3 100644 --- a/services/core/java/com/android/server/devicestate/DeviceState.java +++ b/services/core/java/com/android/server/devicestate/DeviceState.java @@ -76,7 +76,13 @@ public final class DeviceState { * This flag indicates that the corresponding state should be disabled when the device is * overheating and reaching the critical status. */ - public static final int FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL = 1 << 4; + public static final int FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL = 1 << 4; + + /** + * This flag indicates that the corresponding state should be disabled when power save mode + * is enabled. + */ + public static final int FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE = 1 << 5; /** @hide */ @IntDef(prefix = {"FLAG_"}, flag = true, value = { @@ -84,7 +90,8 @@ public final class DeviceState { FLAG_APP_INACCESSIBLE, FLAG_EMULATED_ONLY, FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP, - FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL + FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL, + FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE }) @Retention(RetentionPolicy.SOURCE) public @interface DeviceStateFlags {} diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 43ee5e268fe0..964569008acc 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -25,6 +25,7 @@ import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STA import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS; import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE; import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE; +import static com.android.server.devicestate.OverrideRequestController.FLAG_POWER_SAVE_ENABLED; import static com.android.server.devicestate.OverrideRequestController.FLAG_THERMAL_CRITICAL; import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE; import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED; @@ -609,7 +610,8 @@ public final class DeviceStateManagerService extends SystemService { @GuardedBy("mLock") private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request, - @OverrideRequestController.RequestStatus int status, int flags) { + @OverrideRequestController.RequestStatus int status, + @OverrideRequestController.StatusChangedFlag int flags) { if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_BASE_STATE) { switch (status) { case STATUS_ACTIVE: @@ -641,6 +643,10 @@ public final class DeviceStateManagerService extends SystemService { mDeviceStateNotificationController .showThermalCriticalNotificationIfNeeded( request.getRequestedState()); + } else if ((flags & FLAG_POWER_SAVE_ENABLED) == FLAG_POWER_SAVE_ENABLED) { + mDeviceStateNotificationController + .showPowerSaveNotificationIfNeeded( + request.getRequestedState()); } } break; diff --git a/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java index 900874044881..ab261ac24091 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java @@ -16,6 +16,8 @@ package com.android.server.devicestate; +import static android.provider.Settings.ACTION_BATTERY_SAVER_SETTINGS; + import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; @@ -101,10 +103,16 @@ class DeviceStateNotificationController extends BroadcastReceiver { } String requesterApplicationLabel = getApplicationLabel(requestingAppUid); if (requesterApplicationLabel != null) { + final Intent intent = new Intent(INTENT_ACTION_CANCEL_STATE) + .setPackage(mContext.getPackageName()); + final PendingIntent pendingIntent = PendingIntent.getBroadcast( + mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE); showNotification( info.name, info.activeNotificationTitle, String.format(info.activeNotificationContent, requesterApplicationLabel), - true /* ongoing */, R.drawable.ic_dual_screen + true /* ongoing */, R.drawable.ic_dual_screen, + pendingIntent, + mContext.getString(R.string.device_state_notification_turn_off_button) ); } else { Slog.e(TAG, "Cannot determine the requesting app name when showing state active " @@ -126,7 +134,33 @@ class DeviceStateNotificationController extends BroadcastReceiver { showNotification( info.name, info.thermalCriticalNotificationTitle, info.thermalCriticalNotificationContent, false /* ongoing */, - R.drawable.ic_thermostat + R.drawable.ic_thermostat, + null /* pendingIntent */, + null /* actionText */ + ); + } + + /** + * Displays the notification indicating that the device state is canceled due to power + * save mode being enabled. Does nothing if the state does not have a power save mode + * notification. + * + * @param state the identifier of the device state being canceled. + */ + void showPowerSaveNotificationIfNeeded(int state) { + NotificationInfo info = mNotificationInfos.get(state); + if (info == null || !info.hasPowerSaveModeNotification()) { + return; + } + final Intent intent = new Intent(ACTION_BATTERY_SAVER_SETTINGS); + final PendingIntent pendingIntent = PendingIntent.getActivity( + mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE); + showNotification( + info.name, info.powerSaveModeNotificationTitle, + info.powerSaveModeNotificationContent, false /* ongoing */, + R.drawable.ic_thermostat, + pendingIntent, + mContext.getString(R.string.device_state_notification_settings_button) ); } @@ -161,7 +195,8 @@ class DeviceStateNotificationController extends BroadcastReceiver { */ private void showNotification( @NonNull String name, @NonNull String title, @NonNull String content, boolean ongoing, - @DrawableRes int iconRes) { + @DrawableRes int iconRes, + @Nullable PendingIntent pendingIntent, @Nullable String actionText) { final NotificationChannel channel = new NotificationChannel( CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH); final Notification.Builder builder = new Notification.Builder(mContext, CHANNEL_ID) @@ -173,14 +208,10 @@ class DeviceStateNotificationController extends BroadcastReceiver { .setOngoing(ongoing) .setCategory(Notification.CATEGORY_SYSTEM); - if (ongoing) { - final Intent intent = new Intent(INTENT_ACTION_CANCEL_STATE) - .setPackage(mContext.getPackageName()); - final PendingIntent pendingIntent = PendingIntent.getBroadcast( - mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE); + if (pendingIntent != null && actionText != null) { final Notification.Action action = new Notification.Action.Builder( null /* icon */, - mContext.getString(R.string.device_state_notification_turn_off_button), + actionText, pendingIntent) .build(); builder.addAction(action); @@ -215,12 +246,21 @@ class DeviceStateNotificationController extends BroadcastReceiver { final String[] thermalCriticalNotificationContents = context.getResources().getStringArray( R.array.device_state_notification_thermal_contents); + final String[] powerSaveModeNotificationTitles = + context.getResources().getStringArray( + R.array.device_state_notification_power_save_titles); + final String[] powerSaveModeNotificationContents = + context.getResources().getStringArray( + R.array.device_state_notification_power_save_contents); + if (stateIdentifiers.length != names.length || stateIdentifiers.length != activeNotificationTitles.length || stateIdentifiers.length != activeNotificationContents.length || stateIdentifiers.length != thermalCriticalNotificationTitles.length || stateIdentifiers.length != thermalCriticalNotificationContents.length + || stateIdentifiers.length != powerSaveModeNotificationTitles.length + || stateIdentifiers.length != powerSaveModeNotificationContents.length ) { throw new IllegalStateException( "The length of state identifiers and notification texts must match!"); @@ -237,7 +277,9 @@ class DeviceStateNotificationController extends BroadcastReceiver { new NotificationInfo( names[i], activeNotificationTitles[i], activeNotificationContents[i], thermalCriticalNotificationTitles[i], - thermalCriticalNotificationContents[i]) + thermalCriticalNotificationContents[i], + powerSaveModeNotificationTitles[i], + powerSaveModeNotificationContents[i]) ); } @@ -272,16 +314,21 @@ class DeviceStateNotificationController extends BroadcastReceiver { public final String activeNotificationContent; public final String thermalCriticalNotificationTitle; public final String thermalCriticalNotificationContent; + public final String powerSaveModeNotificationTitle; + public final String powerSaveModeNotificationContent; NotificationInfo(String name, String activeNotificationTitle, String activeNotificationContent, String thermalCriticalNotificationTitle, - String thermalCriticalNotificationContent) { + String thermalCriticalNotificationContent, String powerSaveModeNotificationTitle, + String powerSaveModeNotificationContent) { this.name = name; this.activeNotificationTitle = activeNotificationTitle; this.activeNotificationContent = activeNotificationContent; this.thermalCriticalNotificationTitle = thermalCriticalNotificationTitle; this.thermalCriticalNotificationContent = thermalCriticalNotificationContent; + this.powerSaveModeNotificationTitle = powerSaveModeNotificationTitle; + this.powerSaveModeNotificationContent = powerSaveModeNotificationContent; } boolean hasActiveNotification() { @@ -292,5 +339,10 @@ class DeviceStateNotificationController extends BroadcastReceiver { return thermalCriticalNotificationTitle != null && thermalCriticalNotificationTitle.length() > 0; } + + boolean hasPowerSaveModeNotification() { + return powerSaveModeNotificationTitle != null + && powerSaveModeNotificationTitle.length() > 0; + } } } diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java index fecc13fd0d03..af33de0426b1 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java @@ -52,11 +52,24 @@ public interface DeviceStateProvider { */ int SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL = 3; + /** + * Indicating that the supported device states have changed because power save mode was enabled. + */ + int SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED = 4; + + /** + * Indicating that the supported device states have changed because power save mode was + * disabled. + */ + int SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED = 5; + @IntDef(prefix = { "SUPPORTED_DEVICE_STATES_CHANGED_" }, value = { SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT, SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED, SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL, - SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL + SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL, + SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED, + SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED }) @Retention(RetentionPolicy.SOURCE) @interface SupportedStatesUpdatedReason {} diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java index 2ed4765874f7..46f0bc0d9805 100644 --- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java +++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java @@ -64,6 +64,18 @@ final class OverrideRequestController { */ static final int FLAG_THERMAL_CRITICAL = 1 << 0; + /** + * A flag indicating that the status change was triggered by power save mode. + */ + static final int FLAG_POWER_SAVE_ENABLED = 1 << 1; + + @IntDef(flag = true, prefix = {"FLAG_"}, value = { + FLAG_THERMAL_CRITICAL, + FLAG_POWER_SAVE_ENABLED + }) + @Retention(RetentionPolicy.SOURCE) + @interface StatusChangedFlag {} + static String statusToString(@RequestStatus int status) { switch (status) { case STATUS_ACTIVE: @@ -228,13 +240,18 @@ final class OverrideRequestController { @DeviceStateProvider.SupportedStatesUpdatedReason int reason) { boolean isThermalCritical = reason == DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL; + boolean isPowerSaveEnabled = + reason == DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED; + @StatusChangedFlag int flags = 0; + flags |= isThermalCritical ? FLAG_THERMAL_CRITICAL : 0; + flags |= isPowerSaveEnabled ? FLAG_POWER_SAVE_ENABLED : 0; if (mBaseStateRequest != null && !contains(newSupportedStates, mBaseStateRequest.getRequestedState())) { - cancelCurrentBaseStateRequestLocked(isThermalCritical ? FLAG_THERMAL_CRITICAL : 0); + cancelCurrentBaseStateRequestLocked(flags); } if (mRequest != null && !contains(newSupportedStates, mRequest.getRequestedState())) { - cancelCurrentRequestLocked(isThermalCritical ? FLAG_THERMAL_CRITICAL : 0); + cancelCurrentRequestLocked(flags); } } @@ -255,7 +272,8 @@ final class OverrideRequestController { cancelRequestLocked(requestToCancel, 0 /* flags */); } - private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel, int flags) { + private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel, + @StatusChangedFlag int flags) { mListener.onStatusChanged(requestToCancel, STATUS_CANCELED, flags); } @@ -267,7 +285,7 @@ final class OverrideRequestController { cancelCurrentRequestLocked(0 /* flags */); } - private void cancelCurrentRequestLocked(int flags) { + private void cancelCurrentRequestLocked(@StatusChangedFlag int flags) { if (mRequest == null) { Slog.w(TAG, "Attempted to cancel a null OverrideRequest"); return; @@ -285,7 +303,7 @@ final class OverrideRequestController { cancelCurrentBaseStateRequestLocked(0 /* flags */); } - private void cancelCurrentBaseStateRequestLocked(int flags) { + private void cancelCurrentBaseStateRequestLocked(@StatusChangedFlag int flags) { if (mBaseStateRequest == null) { Slog.w(TAG, "Attempted to cancel a null OverrideRequest"); return; @@ -312,6 +330,6 @@ final class OverrideRequestController { * cancelled request. */ void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus, - int flags); + @StatusChangedFlag int flags); } } diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java index 9f7ff3119dde..0ae1e8076b81 100644 --- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java +++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java @@ -136,17 +136,16 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { mWindowManagerInternal.showImePostLayout(windowToken, statsToken); break; case STATE_HIDE_IME: - if (mService.mCurFocusedWindowClient != null) { + if (mService.hasAttachedClient()) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY); // IMMS only knows of focused window, not the actual IME target. // e.g. it isn't aware of any window that has both // NOT_FOCUSABLE, ALT_FOCUSABLE_IM flags set and can the IME target. - // Send it to window manager to hide IME from IME target window. - // TODO(b/139861270): send to mCurClient.client once IMMS is aware of - // actual IME target. + // Send it to window manager to hide IME from the actual IME control target + // of the target display. mWindowManagerInternal.hideIme(windowToken, - mService.mCurFocusedWindowClient.mSelfReportedDisplayId, statsToken); + mService.getDisplayIdToShowImeLocked(), statsToken); } else { ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index e32bf1beb300..b336b95d793f 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2339,6 +2339,19 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } + /** {@code true} when a {@link ClientState} has attached from starting the input connection. */ + @GuardedBy("ImfLock.class") + boolean hasAttachedClient() { + return mCurClient != null; + } + + @VisibleForTesting + void setAttachedClientForTesting(@NonNull ClientState cs) { + synchronized (ImfLock.class) { + mCurClient = cs; + } + } + @GuardedBy("ImfLock.class") void clearInputShownLocked() { mVisibilityStateComputer.setInputShown(false); diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java index c232b3698bc0..9748aba84c73 100644 --- a/services/core/java/com/android/server/pm/Computer.java +++ b/services/core/java/com/android/server/pm/Computer.java @@ -41,6 +41,7 @@ import android.content.pm.SharedLibraryInfo; import android.content.pm.SigningDetails; import android.content.pm.UserInfo; import android.content.pm.VersionedPackage; +import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; @@ -499,7 +500,7 @@ public interface Computer extends PackageDataSnapshot { String getInstallerPackageName(@NonNull String packageName, @UserIdInt int userId); @Nullable - InstallSourceInfo getInstallSourceInfo(@NonNull String packageName); + InstallSourceInfo getInstallSourceInfo(@NonNull String packageName, @UserIdInt int userId); @PackageManager.EnabledState int getApplicationEnabledSetting(@NonNull String packageName, @UserIdInt int userId); @@ -519,14 +520,15 @@ public interface Computer extends PackageDataSnapshot { * returns false. */ boolean isComponentEffectivelyEnabled(@NonNull ComponentInfo componentInfo, - @UserIdInt int userId); + @NonNull UserHandle userHandle); /** * @return true if the runtime app user enabled state and the install-time app manifest enabled * state are both effectively enabled for the given app. Or if the app cannot be found, * returns false. */ - boolean isApplicationEffectivelyEnabled(@NonNull String packageName, @UserIdInt int userId); + boolean isApplicationEffectivelyEnabled(@NonNull String packageName, + @NonNull UserHandle userHandle); @Nullable KeySet getKeySetByAlias(@NonNull String packageName, @NonNull String alias); diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 5984360a534c..acd4a96c2817 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -4982,9 +4982,11 @@ public class ComputerEngine implements Computer { @Override @Nullable - public InstallSourceInfo getInstallSourceInfo(@NonNull String packageName) { + public InstallSourceInfo getInstallSourceInfo(@NonNull String packageName, + @UserIdInt int userId) { final int callingUid = Binder.getCallingUid(); - final int userId = UserHandle.getUserId(callingUid); + enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */, + false /* checkShell */, "getInstallSourceInfo"); String installerPackageName; String initiatingPackageName; @@ -5129,9 +5131,10 @@ public class ComputerEngine implements Computer { @Override public boolean isComponentEffectivelyEnabled(@NonNull ComponentInfo componentInfo, - @UserIdInt int userId) { + @NonNull UserHandle userHandle) { try { String packageName = componentInfo.packageName; + int userId = userHandle.getIdentifier(); int appEnabledSetting = mSettings.getApplicationEnabledSetting(packageName, userId); if (appEnabledSetting == COMPONENT_ENABLED_STATE_DEFAULT) { @@ -5154,9 +5157,10 @@ public class ComputerEngine implements Computer { @Override public boolean isApplicationEffectivelyEnabled(@NonNull String packageName, - @UserIdInt int userId) { + @NonNull UserHandle userHandle) { try { - int appEnabledSetting = mSettings.getApplicationEnabledSetting(packageName, userId); + int appEnabledSetting = mSettings.getApplicationEnabledSetting(packageName, + userHandle.getIdentifier()); if (appEnabledSetting == COMPONENT_ENABLED_STATE_DEFAULT) { final AndroidPackage pkg = getPackage(packageName); if (pkg == null) { diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java index d39cac070413..c29e4d78f929 100644 --- a/services/core/java/com/android/server/pm/IPackageManagerBase.java +++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java @@ -463,8 +463,9 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub { @Override @Nullable @Deprecated - public final InstallSourceInfo getInstallSourceInfo(@NonNull String packageName) { - return snapshot().getInstallSourceInfo(packageName); + public final InstallSourceInfo getInstallSourceInfo(@NonNull String packageName, + @UserIdInt int userId) { + return snapshot().getInstallSourceInfo(packageName, userId); } @Override diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index fa535c38c5d2..03e0d360f9e3 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -925,7 +925,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final int targetPackageUid = snapshot.getPackageUid(packageName, 0, userId); final boolean isUpdate = targetPackageUid != -1 || isApexSession(); final InstallSourceInfo existingInstallSourceInfo = isUpdate - ? snapshot.getInstallSourceInfo(packageName) + ? snapshot.getInstallSourceInfo(packageName, userId) : null; final String existingInstallerPackageName = existingInstallSourceInfo != null ? existingInstallSourceInfo.getInstallingPackageName() diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 4d74567cfeaf..6bc876037cfb 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1328,7 +1328,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService throw new ParcelableException(new PackageManager.NameNotFoundException(packageName)); } - final InstallSourceInfo installSourceInfo = snapshot.getInstallSourceInfo(packageName); + final InstallSourceInfo installSourceInfo = snapshot.getInstallSourceInfo(packageName, + userId); final String installerPackageName; if (installSourceInfo != null) { if (!TextUtils.isEmpty(installSourceInfo.getInitiatingPackageName())) { @@ -2569,7 +2570,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService if (best == null || cur.priority > best.priority) { if (computer.isComponentEffectivelyEnabled(cur.getComponentInfo(), - UserHandle.USER_SYSTEM)) { + UserHandle.SYSTEM)) { best = cur; } else { Slog.w(TAG, "Domain verification agent found but not enabled"); diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java index 5b967ec20cd3..f340f9374dd5 100644 --- a/services/core/java/com/android/server/pm/VerifyingSession.java +++ b/services/core/java/com/android/server/pm/VerifyingSession.java @@ -26,7 +26,6 @@ import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4; import static android.os.PowerWhitelistManager.REASON_PACKAGE_VERIFIER; import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; -import static android.os.Process.SYSTEM_UID; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static com.android.server.pm.PackageManagerService.CHECK_PENDING_INTEGRITY_VERIFICATION; @@ -408,7 +407,7 @@ final class VerifyingSession { final int numRequiredVerifierPackages = requiredVerifierPackages.size(); for (int i = numRequiredVerifierPackages - 1; i >= 0; i--) { if (!snapshot.isApplicationEffectivelyEnabled(requiredVerifierPackages.get(i), - SYSTEM_UID)) { + verifierUser)) { Slog.w(TAG, "Required verifier: " + requiredVerifierPackages.get(i) + " is disabled"); requiredVerifierPackages.remove(i); diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java index 8d7f78209a4e..3644054e3b78 100644 --- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java +++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java @@ -21,7 +21,10 @@ import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STA import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; @@ -101,8 +104,10 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, private static final String FLAG_EMULATED_ONLY = "FLAG_EMULATED_ONLY"; private static final String FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP = "FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP"; - private static final String FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL = - "FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL"; + private static final String FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL = + "FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL"; + private static final String FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE = + "FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE"; /** Interface that allows reading the device state configuration. */ interface ReadableConfig { @@ -162,9 +167,12 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, case FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP: flags |= DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP; break; - case FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL: - flags |= DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL; + case FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL: + flags |= DeviceState + .FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL; break; + case FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE: + flags |= DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE; default: Slog.w(TAG, "Parsed unknown flag with name: " + configFlagString); @@ -210,6 +218,9 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, @GuardedBy("mLock") private @PowerManager.ThermalStatus int mThermalStatus = PowerManager.THERMAL_STATUS_NONE; + @GuardedBy("mLock") + private boolean mPowerSaveModeEnabled; + private DeviceStateProviderImpl(@NonNull Context context, @NonNull List<DeviceState> deviceStates, @NonNull List<Conditions> stateConditions) { @@ -224,14 +235,32 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, setStateConditions(deviceStates, stateConditions); - // If any of the device states are thermal sensitive, i.e. it should be disabled when the - // device is overheating, then we will update the list of supported states when thermal - // status changes. - if (hasThermalSensitiveState(deviceStates)) { - PowerManager powerManager = context.getSystemService(PowerManager.class); - if (powerManager != null) { + PowerManager powerManager = context.getSystemService(PowerManager.class); + if (powerManager != null) { + // If any of the device states are thermal sensitive, i.e. it should be disabled when + // the device is overheating, then we will update the list of supported states when + // thermal status changes. + if (hasThermalSensitiveState(deviceStates)) { powerManager.addThermalStatusListener(this); } + + // If any of the device states are power sensitive, i.e. it should be disabled when + // power save mode is enabled, then we will update the list of supported states when + // power save mode is toggled. + if (hasPowerSaveSensitiveState(deviceStates)) { + IntentFilter filter = new IntentFilter( + PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL); + BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL.equals( + intent.getAction())) { + onPowerSaveModeChanged(powerManager.isPowerSaveMode()); + } + } + }; + mContext.registerReceiver(receiver, filter); + } } } @@ -382,7 +411,11 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, for (DeviceState deviceState : mOrderedStates) { if (isThermalStatusCriticalOrAbove(mThermalStatus) && deviceState.hasFlag( - DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL)) { + DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) { + continue; + } + if (mPowerSaveModeEnabled && deviceState.hasFlag( + DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) { continue; } supportedStates.add(deviceState); @@ -674,6 +707,18 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, } } + @VisibleForTesting + void onPowerSaveModeChanged(boolean isPowerSaveModeEnabled) { + synchronized (mLock) { + if (mPowerSaveModeEnabled != isPowerSaveModeEnabled) { + mPowerSaveModeEnabled = isPowerSaveModeEnabled; + notifySupportedStatesChanged( + isPowerSaveModeEnabled ? SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED + : SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED); + } + } + } + @Override public void onThermalStatusChanged(@PowerManager.ThermalStatus int thermalStatus) { int previousThermalStatus; @@ -709,7 +754,16 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, private static boolean hasThermalSensitiveState(List<DeviceState> deviceStates) { for (DeviceState state : deviceStates) { - if (state.hasFlag(DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL)) { + if (state.hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) { + return true; + } + } + return false; + } + + private static boolean hasPowerSaveSensitiveState(List<DeviceState> deviceStates) { + for (int i = 0; i < deviceStates.size(); i++) { + if (deviceStates.get(i).hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) { return true; } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 324a0adee693..e21c15665140 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -4357,17 +4357,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override void addWindow(WindowState w) { super.addWindow(w); - - boolean gotReplacementWindow = false; - for (int i = mChildren.size() - 1; i >= 0; i--) { - final WindowState candidate = mChildren.get(i); - gotReplacementWindow |= candidate.setReplacementWindowIfNeeded(w); - } - - // if we got a replacement window, reset the timeout to give drawing more time - if (gotReplacementWindow) { - mWmService.scheduleWindowReplacementTimeouts(this); - } checkKeyguardFlagsChanged(); } @@ -4382,12 +4371,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A updateLetterboxSurface(child); } - void onWindowReplacementTimeout() { - for (int i = mChildren.size() - 1; i >= 0; --i) { - (mChildren.get(i)).onWindowReplacementTimeout(); - } - } - void setAppLayoutChanges(int changes, String reason) { if (!mChildren.isEmpty()) { final DisplayContent dc = getDisplayContent(); @@ -4398,15 +4381,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - void removeReplacedWindowIfNeeded(WindowState replacement) { - for (int i = mChildren.size() - 1; i >= 0; i--) { - final WindowState win = mChildren.get(i); - if (win.removeReplacedWindowIfNeeded(replacement)) { - return; - } - } - } - private boolean transferStartingWindow(@NonNull ActivityRecord fromActivity) { final WindowState tStartingWindow = fromActivity.mStartingWindow; if (tStartingWindow != null && fromActivity.mStartingSurface != null) { @@ -5633,10 +5607,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // * activity is transitioning visibility state // * or the activity was marked as hidden and is exiting before we had a chance to play the // transition animation - // * or this is an opening app and windows are being replaced (e.g. freeform window to - // normal window). - return isVisible() != visible || mRequestForceTransition || (!isVisible() && mIsExiting) - || (visible && forAllWindows(WindowState::waitingForReplacement, true)); + return isVisible() != visible || mRequestForceTransition || (!isVisible() && mIsExiting); } /** @@ -7380,35 +7351,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - void setWillReplaceWindows(boolean animate) { - ProtoLog.d(WM_DEBUG_ADD_REMOVE, - "Marking app token %s with replacing windows.", this); - - for (int i = mChildren.size() - 1; i >= 0; i--) { - final WindowState w = mChildren.get(i); - w.setWillReplaceWindow(animate); - } - } - - void setWillReplaceChildWindows() { - ProtoLog.d(WM_DEBUG_ADD_REMOVE, "Marking app token %s" - + " with replacing child windows.", this); - for (int i = mChildren.size() - 1; i >= 0; i--) { - final WindowState w = mChildren.get(i); - w.setWillReplaceChildWindows(); - } - } - - void clearWillReplaceWindows() { - ProtoLog.d(WM_DEBUG_ADD_REMOVE, - "Resetting app token %s of replacing window marks.", this); - - for (int i = mChildren.size() - 1; i >= 0; i--) { - final WindowState w = mChildren.get(i); - w.clearWillReplaceWindow(); - } - } - void requestUpdateWallpaperIfNeeded() { for (int i = mChildren.size() - 1; i >= 0; i--) { final WindowState w = mChildren.get(i); diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index 0b28ba2bb146..bc9efc88ecae 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -377,15 +377,10 @@ class DragState { mDragWindowHandle.ownerUid = MY_UID; mDragWindowHandle.scaleFactor = 1.0f; - // InputConfig.PREVENT_SPLITTING: To keep the default behavior of this window to be - // focusable, which allows the system to consume keys when dragging is active. This can - // also be used to modify the drag state on key press. For example, cancel drag on - // escape key. // InputConfig.TRUSTED_OVERLAY: To not block any touches while D&D ongoing and allowing // touches to pass through to windows underneath. This allows user to interact with the // UI to navigate while dragging. - mDragWindowHandle.inputConfig = - InputConfig.PREVENT_SPLITTING | InputConfig.TRUSTED_OVERLAY; + mDragWindowHandle.inputConfig = InputConfig.TRUSTED_OVERLAY; // The drag window cannot receive new touches. mDragWindowHandle.touchableRegion.setEmpty(); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index b2a4df1f4692..fda2125be3cf 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -44,7 +44,6 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC; -import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; @@ -432,13 +431,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } }; - private static final Consumer<WindowState> sRemoveReplacedWindowsConsumer = w -> { - final ActivityRecord activity = w.mActivityRecord; - if (activity != null) { - activity.removeReplacedWindowIfNeeded(w); - } - }; - RootWindowContainer(WindowManagerService service) { super(service); mHandler = new MyHandler(service.mH.getLooper()); @@ -662,17 +654,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> forAllWindows(mCloseSystemDialogsConsumer, false /* traverseTopToBottom */); } - void removeReplacedWindows() { - ProtoLog.i(WM_SHOW_TRANSACTIONS, ">>> OPEN TRANSACTION removeReplacedWindows"); - mWmService.openSurfaceTransaction(); - try { - forAllWindows(sRemoveReplacedWindowsConsumer, true /* traverseTopToBottom */); - } finally { - mWmService.closeSurfaceTransaction("removeReplacedWindows"); - ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION removeReplacedWindows"); - } - } - boolean hasPendingLayoutChanges(WindowAnimator animator) { boolean hasChanges = false; diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index ce9bff8521e6..b1ca72be87bb 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -232,11 +232,6 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } @Override - public void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly) { - mService.setWillReplaceWindows(appToken, childrenOnly); - } - - @Override public boolean cancelDraw(IWindow window) { return mService.cancelDraw(this, window); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 67ca8443102b..6882e4c1d557 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -866,23 +866,8 @@ class Task extends TaskFragment { return false; } - final int toRootTaskWindowingMode = toRootTask.getWindowingMode(); final ActivityRecord topActivity = getTopNonFinishingActivity(); - final boolean mightReplaceWindow = topActivity != null - && replaceWindowsOnTaskMove(getWindowingMode(), toRootTaskWindowingMode); - if (mightReplaceWindow) { - // We are about to relaunch the activity because its configuration changed due to - // being maximized, i.e. size change. The activity will first remove the old window - // and then add a new one. This call will tell window manager about this, so it can - // preserve the old window until the new one is drawn. This prevents having a gap - // between the removal and addition, in which no window is visible. We also want the - // entrance of the new window to be properly animated. - // Note here we always set the replacing window first, as the flags might be needed - // during the relaunch. If we end up not doing any relaunch, we clear the flags later. - windowManager.setWillReplaceWindow(topActivity.token, animate); - } - mAtmService.deferWindowLayout(); boolean kept = true; try { @@ -926,17 +911,10 @@ class Task extends TaskFragment { mAtmService.continueWindowLayout(); } - if (mightReplaceWindow) { - // If we didn't actual do a relaunch (indicated by kept==true meaning we kept the old - // window), we need to clear the replace window settings. Otherwise, we schedule a - // timeout to remove the old window if the replacing window is not coming in time. - windowManager.scheduleClearWillReplaceWindows(topActivity.token, !kept); - } - if (!deferResume) { // The task might have already been running and its visibility needs to be synchronized // with the visibility of the root task / windows. - root.ensureActivitiesVisible(null, 0, !mightReplaceWindow); + root.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); root.resumeFocusedTasksTopActivities(); } @@ -947,17 +925,6 @@ class Task extends TaskFragment { return (preferredRootTask == toRootTask); } - /** - * @return {@code true} if the windows of tasks being moved to the target root task from the - * source root task should be replaced, meaning that window manager will keep the old window - * around until the new is ready. - */ - private static boolean replaceWindowsOnTaskMove( - int sourceWindowingMode, int targetWindowingMode) { - return sourceWindowingMode == WINDOWING_MODE_FREEFORM - || targetWindowingMode == WINDOWING_MODE_FREEFORM; - } - void touchActiveTime() { lastActiveTime = SystemClock.elapsedRealtime(); } diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 2b848d57e2f9..0b9ceeaf5d4e 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -163,14 +163,6 @@ class WallpaperController { if (DEBUG_WALLPAPER) Slog.v(TAG, "Win " + w + ": isOnScreen=" + w.isOnScreen() + " mDrawState=" + w.mWinAnimator.mDrawState); - if (w.mWillReplaceWindow && mWallpaperTarget == null - && !mFindResults.useTopWallpaperAsTarget) { - // When we are replacing a window and there was wallpaper before replacement, we want to - // keep the window until the new windows fully appear and can determine the visibility, - // to avoid flickering. - mFindResults.setUseTopWallpaperAsTarget(true); - } - final WindowContainer animatingContainer = w.mActivityRecord != null ? w.mActivityRecord.getAnimatingContainer() : null; final boolean keyguardGoingAwayWithWallpaper = (animatingContainer != null diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index c11391e1236e..25965331241e 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -69,10 +69,6 @@ public class WindowAnimator { SparseArray<DisplayContentsAnimator> mDisplayContentsAnimators = new SparseArray<>(2); private boolean mInitialized = false; - // When set to true the animator will go over all windows after an animation frame is posted and - // check if some got replaced and can be removed. - private boolean mRemoveReplacedWindows = false; - private Choreographer mChoreographer; /** @@ -217,11 +213,6 @@ public class WindowAnimator { mService.closeSurfaceTransaction("WindowAnimator"); ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION animate"); - if (mRemoveReplacedWindows) { - root.removeReplacedWindows(); - mRemoveReplacedWindows = false; - } - mService.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); executeAfterPrepareSurfacesRunnables(); @@ -286,10 +277,6 @@ public class WindowAnimator { return displayAnimator; } - void requestRemovalOfReplacedWindows(WindowState win) { - mRemoveReplacedWindows = true; - } - void scheduleAnimation() { if (!mAnimationFrameCallbackScheduled) { mAnimationFrameCallbackScheduled = true; diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 2f3a70eb0e2d..969afe544b18 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -740,7 +740,7 @@ public abstract class WindowManagerInternal { /** * Show IME on imeTargetWindow once IME has finished layout. * - * @param imeTargetWindowToken token of the (IME target) window on which IME should be shown. + * @param imeTargetWindowToken token of the (IME target) window which IME should be shown. * @param statsToken the token tracking the current IME show request or {@code null} otherwise. */ public abstract void showImePostLayout(IBinder imeTargetWindowToken, @@ -749,7 +749,7 @@ public abstract class WindowManagerInternal { /** * Hide IME using imeTargetWindow when requested. * - * @param imeTargetWindowToken token of the (IME target) window on which IME should be hidden. + * @param imeTargetWindowToken token of the (IME target) window on which requests hiding IME. * @param displayId the id of the display the IME is on. * @param statsToken the token tracking the current IME hide request or {@code null} otherwise. */ diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 42d23e755b21..8582356f4d9e 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -88,7 +88,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED; import static android.view.WindowManager.TRANSIT_NONE; -import static android.view.WindowManager.TRANSIT_RELAUNCH; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.fixScale; import static android.view.WindowManagerGlobal.ADD_OKAY; @@ -382,9 +381,6 @@ public class WindowManagerService extends IWindowManager.Stub /** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */ static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000; - /** Amount of time (in milliseconds) to delay before declaring a window replacement timeout. */ - static final int WINDOW_REPLACEMENT_TIMEOUT_DURATION = 2000; - /** Amount of time to allow a last ANR message to exist before freeing the memory. */ static final int LAST_ANR_LIFETIME_DURATION_MSECS = 2 * 60 * 60 * 1000; // Two hours @@ -565,12 +561,6 @@ public class WindowManagerService extends IWindowManager.Stub final WindowManagerGlobalLock mGlobalLock; /** - * List of app window tokens that are waiting for replacing windows. If the - * replacement doesn't come in time the stale windows needs to be disposed of. - */ - final ArrayList<ActivityRecord> mWindowReplacementTimeouts = new ArrayList<>(); - - /** * Windows that are being resized. Used so we can tell the client about * the resize after closing the transaction in which we resized the * underlying surface. @@ -1774,15 +1764,6 @@ public class WindowManagerService extends IWindowManager.Stub final WindowStateAnimator winAnimator = win.mWinAnimator; winAnimator.mEnterAnimationPending = true; winAnimator.mEnteringAnimation = true; - // Check if we need to prepare a transition for replacing window first. - if (!win.mTransitionController.isShellTransitionsEnabled() - && activity != null && activity.isVisible() - && !prepareWindowReplacementTransition(activity)) { - // If not, check if need to set up a dummy transition during display freeze - // so that the unfreeze wait for the apps to draw. This might be needed if - // the app is relaunching. - prepareNoneTransitionForRelaunching(activity); - } if (displayPolicy.areSystemBarsForcedConsumedLw()) { res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS; @@ -1944,48 +1925,6 @@ public class WindowManagerService extends IWindowManager.Stub } /** - * Returns true if we're done setting up any transitions. - */ - private boolean prepareWindowReplacementTransition(ActivityRecord activity) { - activity.clearAllDrawn(); - final WindowState replacedWindow = activity.getReplacingWindow(); - if (replacedWindow == null) { - // We expect to already receive a request to remove the old window. If it did not - // happen, let's just simply add a window. - return false; - } - // We use the visible frame, because we want the animation to morph the window from what - // was visible to the user to the final destination of the new window. - final Rect frame = new Rect(replacedWindow.getFrame()); - final WindowManager.LayoutParams attrs = replacedWindow.mAttrs; - frame.inset(replacedWindow.getInsetsStateWithVisibilityOverride().calculateVisibleInsets( - frame, attrs.type, replacedWindow.getWindowingMode(), attrs.softInputMode, - attrs.flags)); - // We treat this as if this activity was opening, so we can trigger the app transition - // animation and piggy-back on existing transition animation infrastructure. - final DisplayContent dc = activity.getDisplayContent(); - dc.mOpeningApps.add(activity); - dc.prepareAppTransition(TRANSIT_RELAUNCH); - dc.mAppTransition.overridePendingAppTransitionClipReveal(frame.left, frame.top, - frame.width(), frame.height()); - dc.executeAppTransition(); - return true; - } - - private void prepareNoneTransitionForRelaunching(ActivityRecord activity) { - // Set up a none-transition and add the app to opening apps, so that the display - // unfreeze wait for the apps to be drawn. - // Note that if the display unfroze already because app unfreeze timed out, - // we don't set up the transition anymore and just let it go. - final DisplayContent dc = activity.getDisplayContent(); - if (mDisplayFrozen && !dc.mOpeningApps.contains(activity) && activity.isRelaunching()) { - dc.mOpeningApps.add(activity); - dc.prepareAppTransition(TRANSIT_NONE); - dc.executeAppTransition(); - } - } - - /** * Set whether screen capture is disabled for all windows of a specific user from * the device policy cache. */ @@ -2390,12 +2329,6 @@ public class WindowManagerService extends IWindowManager.Stub // If we are not currently running the exit animation, we need to see about starting // one. - // We don't want to animate visibility of windows which are pending replacement. - // In the case of activity relaunch child windows could request visibility changes as - // they are detached from the main application window during the tear down process. - // If we satisfied these visibility changes though, we would cause a visual glitch - // hiding the window before it's replacement was available. So we just do nothing on - // our side. // This must be called before the call to performSurfacePlacement. if (!shouldRelayout && winAnimator.hasSurface() && !win.mAnimatingExit) { if (DEBUG_VISIBILITY) { @@ -2403,20 +2336,18 @@ public class WindowManagerService extends IWindowManager.Stub "Relayout invis " + win + ": mAnimatingExit=" + win.mAnimatingExit); } result |= RELAYOUT_RES_SURFACE_CHANGED; - if (!win.mWillReplaceWindow) { - // When FLAG_SHOW_WALLPAPER flag is removed from a window, we usually set a flag - // in DC#pendingLayoutChanges and update the wallpaper target later. - // However it's possible that FLAG_SHOW_WALLPAPER flag is removed from a window - // when the window is about to exit, so we update the wallpaper target - // immediately here. Otherwise this window will be stuck in exiting and its - // surface remains on the screen. - // TODO(b/189856716): Allow destroying surface even if it belongs to the - // keyguard target. - if (wallpaperMayMove) { - displayContent.mWallpaperController.adjustWallpaperWindows(); - } - tryStartExitingAnimation(win, winAnimator); + // When FLAG_SHOW_WALLPAPER flag is removed from a window, we usually set a flag + // in DC#pendingLayoutChanges and update the wallpaper target later. + // However it's possible that FLAG_SHOW_WALLPAPER flag is removed from a window + // when the window is about to exit, so we update the wallpaper target + // immediately here. Otherwise this window will be stuck in exiting and its + // surface remains on the screen. + // TODO(b/189856716): Allow destroying surface even if it belongs to the + // keyguard target. + if (wallpaperMayMove) { + displayContent.mWallpaperController.adjustWallpaperWindows(); } + tryStartExitingAnimation(win, winAnimator); } // Create surfaceControl before surface placement otherwise layout will be skipped @@ -5328,8 +5259,6 @@ public class WindowManagerService extends IWindowManager.Stub public static final int UPDATE_MULTI_WINDOW_STACKS = 41; - public static final int WINDOW_REPLACEMENT_TIMEOUT = 46; - public static final int UPDATE_ANIMATION_SCALE = 51; public static final int WINDOW_HIDE_TIMEOUT = 52; public static final int RESTORE_POINTER_ICON = 55; @@ -5555,16 +5484,6 @@ public class WindowManagerService extends IWindowManager.Stub } break; } - case WINDOW_REPLACEMENT_TIMEOUT: { - synchronized (mGlobalLock) { - for (int i = mWindowReplacementTimeouts.size() - 1; i >= 0; i--) { - final ActivityRecord activity = mWindowReplacementTimeouts.get(i); - activity.onWindowReplacementTimeout(); - } - mWindowReplacementTimeouts.clear(); - } - break; - } case WINDOW_HIDE_TIMEOUT: { final WindowState window = (WindowState) msg.obj; synchronized (mGlobalLock) { @@ -7083,98 +7002,6 @@ public class WindowManagerService extends IWindowManager.Stub return mGlobalLock; } - /** - * Hint to a token that its activity will relaunch, which will trigger removal and addition of - * a window. - * - * @param token Application token for which the activity will be relaunched. - */ - void setWillReplaceWindow(IBinder token, boolean animate) { - final ActivityRecord activity = mRoot.getActivityRecord(token); - if (activity == null) { - ProtoLog.w(WM_ERROR, "Attempted to set replacing window on non-existing app token %s", - token); - return; - } - if (!activity.hasContentToDisplay()) { - ProtoLog.w(WM_ERROR, - "Attempted to set replacing window on app token with no content %s", - token); - return; - } - activity.setWillReplaceWindows(animate); - } - - /** - * Hint to a token that its windows will be replaced across activity relaunch. - * The windows would otherwise be removed shortly following this as the - * activity is torn down. - * @param token Application token for which the activity will be relaunched. - * @param childrenOnly Whether to mark only child windows for replacement - * (for the case where main windows are being preserved/ - * reused rather than replaced). - * - */ - // TODO: The s at the end of the method name is the only difference with the name of the method - // above. We should combine them or find better names. - void setWillReplaceWindows(IBinder token, boolean childrenOnly) { - synchronized (mGlobalLock) { - final ActivityRecord activity = mRoot.getActivityRecord(token); - if (activity == null) { - ProtoLog.w(WM_ERROR, - "Attempted to set replacing window on non-existing app token %s", - token); - return; - } - if (!activity.hasContentToDisplay()) { - ProtoLog.w(WM_ERROR, - "Attempted to set replacing window on app token with no content %s", - token); - return; - } - - if (childrenOnly) { - activity.setWillReplaceChildWindows(); - } else { - activity.setWillReplaceWindows(false /* animate */); - } - - scheduleClearWillReplaceWindows(token, true /* replacing */); - } - } - - /** - * If we're replacing the window, schedule a timer to clear the replaced window - * after a timeout, in case the replacing window is not coming. - * - * If we're not replacing the window, clear the replace window settings of the app. - * - * @param token Application token for the activity whose window might be replaced. - * @param replacing Whether the window is being replaced or not. - */ - void scheduleClearWillReplaceWindows(IBinder token, boolean replacing) { - final ActivityRecord activity = mRoot.getActivityRecord(token); - if (activity == null) { - ProtoLog.w(WM_ERROR, "Attempted to reset replacing window on non-existing app token %s", - token); - return; - } - if (replacing) { - scheduleWindowReplacementTimeouts(activity); - } else { - activity.clearWillReplaceWindows(); - } - } - - void scheduleWindowReplacementTimeouts(ActivityRecord activity) { - if (!mWindowReplacementTimeouts.contains(activity)) { - mWindowReplacementTimeouts.add(activity); - } - mH.removeMessages(H.WINDOW_REPLACEMENT_TIMEOUT); - mH.sendEmptyMessageDelayed( - H.WINDOW_REPLACEMENT_TIMEOUT, WINDOW_REPLACEMENT_TIMEOUT_DURATION); - } - @Override public int getDockedStackSide() { return 0; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index d6c03113e87f..8a083aa6220f 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -59,13 +59,11 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NOT_MAGNIFIAB import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; @@ -632,22 +630,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP boolean mHasSurface = false; - // This window will be replaced due to relaunch. This allows window manager - // to differentiate between simple removal of a window and replacement. In the latter case it - // will preserve the old window until the new one is drawn. - boolean mWillReplaceWindow = false; - // If true, the replaced window was already requested to be removed. - private boolean mReplacingRemoveRequested = false; - // Whether the replacement of the window should trigger app transition animation. - private boolean mAnimateReplacingWindow = false; - // If not null, the window that will be used to replace the old one. This is being set when - // the window is added and unset when this window reports its first draw. - private WindowState mReplacementWindow = null; - // For the new window in the replacement transition, if we have - // requested to replace without animation, then we should - // make sure we also don't apply an enter animation for - // the new window. - boolean mSkipEnterAnimationForSeamlessReplacement = false; // Whether this window is being moved via the resize API private boolean mMovedByResize; @@ -1309,13 +1291,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } boolean skipLayout() { - if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) { - // This window is being replaced and either already got information that it's being - // removed or we are still waiting for some information. Because of this we don't - // want to apply any more changes to it, so it remains in this state until new window - // appears. - return true; - } // Skip layout of the window when in transition to pip mode. return mActivityRecord != null && mActivityRecord.mWaitForEnteringPinnedMode; } @@ -2354,24 +2329,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } - void onWindowReplacementTimeout() { - if (mWillReplaceWindow) { - // Since the window already timed out, remove it immediately now. - // Use WindowState#removeImmediately() instead of WindowState#removeIfPossible(), as - // the latter delays removal on certain conditions, which will leave the stale window - // in the root task and marked mWillReplaceWindow=false, so the window will never be - // removed. - // - // Also removes child windows. - removeImmediately(); - } else { - for (int i = mChildren.size() - 1; i >= 0; --i) { - final WindowState c = mChildren.get(i); - c.onWindowReplacementTimeout(); - } - } - } - @Override void removeImmediately() { if (mRemoved) { @@ -2388,11 +2345,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWinAnimator.destroySurfaceLocked(getSyncTransaction()); super.removeImmediately(); - mWillReplaceWindow = false; - if (mReplacementWindow != null) { - mReplacementWindow.mSkipEnterAnimationForSeamlessReplacement = false; - } - final DisplayContent dc = getDisplayContent(); if (isImeLayeringTarget()) { // Remove the attached IME screenshot surface. @@ -2472,12 +2424,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b " + "mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b " - + "mWillReplaceWindow=%b mDisplayFrozen=%b callers=%s", + + "mDisplayFrozen=%b callers=%s", this, mWinAnimator.mSurfaceController, mAnimatingExit, mRemoveOnExit, mHasSurface, mWinAnimator.getShown(), isAnimating(TRANSITION | PARENTS), mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION), - mWillReplaceWindow, mWmService.mDisplayFrozen, Debug.getCallers(6)); // Visibility of the removed window. Will be used later to update orientation later on. @@ -2487,22 +2438,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // window until the animation is done. If the display is frozen, just remove immediately, // since the animation wouldn't be seen. if (mHasSurface && mToken.okToAnimate()) { - if (mWillReplaceWindow) { - // This window is going to be replaced. We need to keep it around until the new one - // gets added, then we will get rid of this one. - ProtoLog.v(WM_DEBUG_ADD_REMOVE, - "Preserving %s until the new one is added", this); - // TODO: We are overloading mAnimatingExit flag to prevent the window state from - // been removed. We probably need another flag to indicate that window removal - // should be deffered vs. overloading the flag that says we are playing an exit - // animation. - ProtoLog.v(WM_DEBUG_ANIM, - "Set animatingExit: reason=remove/replaceWindow win=%s", this); - mAnimatingExit = true; - mReplacingRemoveRequested = true; - return; - } - // If we are not currently running the exit animation, we need to see about starting one wasVisible = isVisible(); @@ -2714,53 +2649,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mInputWindowHandle.setToken(null); } - /** Returns true if the replacement window was removed. */ - boolean removeReplacedWindowIfNeeded(WindowState replacement) { - if (mWillReplaceWindow && mReplacementWindow == replacement && replacement.hasDrawn()) { - replacement.mSkipEnterAnimationForSeamlessReplacement = false; - removeReplacedWindow(); - return true; - } - - for (int i = mChildren.size() - 1; i >= 0; --i) { - final WindowState c = mChildren.get(i); - if (c.removeReplacedWindowIfNeeded(replacement)) { - return true; - } - } - return false; - } - - private void removeReplacedWindow() { - ProtoLog.d(WM_DEBUG_ADD_REMOVE, "Removing replaced window: %s", this); - mWillReplaceWindow = false; - mAnimateReplacingWindow = false; - mReplacingRemoveRequested = false; - mReplacementWindow = null; - if (mAnimatingExit || !mAnimateReplacingWindow) { - removeImmediately(); - } - } - - boolean setReplacementWindowIfNeeded(WindowState replacementCandidate) { - boolean replacementSet = false; - - if (mWillReplaceWindow && mReplacementWindow == null - && getWindowTag().toString().equals(replacementCandidate.getWindowTag().toString())) { - - mReplacementWindow = replacementCandidate; - replacementCandidate.mSkipEnterAnimationForSeamlessReplacement = !mAnimateReplacingWindow; - replacementSet = true; - } - - for (int i = mChildren.size() - 1; i >= 0; --i) { - final WindowState c = mChildren.get(i); - replacementSet |= c.setReplacementWindowIfNeeded(replacementCandidate); - } - - return replacementSet; - } - void setDisplayLayoutNeeded() { final DisplayContent dc = getDisplayContent(); if (dc != null) { @@ -4391,49 +4279,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return parent != null && parent.isGoneForLayout(); } - void setWillReplaceWindow(boolean animate) { - for (int i = mChildren.size() - 1; i >= 0; i--) { - final WindowState c = mChildren.get(i); - c.setWillReplaceWindow(animate); - } - - if ((mAttrs.privateFlags & PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH) != 0 - || mAttrs.type == TYPE_APPLICATION_STARTING) { - // We don't set replacing on starting windows since they are added by window manager and - // not the client so won't be replaced by the client. - return; - } - - mWillReplaceWindow = true; - mReplacementWindow = null; - mAnimateReplacingWindow = animate; - } - - void clearWillReplaceWindow() { - mWillReplaceWindow = false; - mReplacementWindow = null; - mAnimateReplacingWindow = false; - - for (int i = mChildren.size() - 1; i >= 0; i--) { - final WindowState c = mChildren.get(i); - c.clearWillReplaceWindow(); - } - } - - boolean waitingForReplacement() { - if (mWillReplaceWindow) { - return true; - } - - for (int i = mChildren.size() - 1; i >= 0; i--) { - final WindowState c = mChildren.get(i); - if (c.waitingForReplacement()) { - return true; - } - } - return false; - } - void requestUpdateWallpaperIfNeeded() { final DisplayContent dc = getDisplayContent(); if (dc != null && ((mIsWallpaper && !mLastConfigReportedToClient) || hasWallpaper())) { @@ -4464,43 +4309,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return winY; } - // During activity relaunch due to resize, we sometimes use window replacement - // for only child windows (as the main window is handled by window preservation) - // and the big surface. - // - // Though windows of TYPE_APPLICATION or TYPE_DRAWN_APPLICATION (as opposed to - // TYPE_BASE_APPLICATION) are not children in the sense of an attached window, - // we also want to replace them at such phases, as they won't be covered by window - // preservation, and in general we expect them to return following relaunch. - boolean shouldBeReplacedWithChildren() { - return mIsChildWindow || mAttrs.type == TYPE_APPLICATION - || mAttrs.type == TYPE_DRAWN_APPLICATION; - } - - void setWillReplaceChildWindows() { - if (shouldBeReplacedWithChildren()) { - setWillReplaceWindow(false /* animate */); - } - for (int i = mChildren.size() - 1; i >= 0; i--) { - final WindowState c = mChildren.get(i); - c.setWillReplaceChildWindows(); - } - } - - WindowState getReplacingWindow() { - if (mAnimatingExit && mWillReplaceWindow && mAnimateReplacingWindow) { - return this; - } - for (int i = mChildren.size() - 1; i >= 0; i--) { - final WindowState c = mChildren.get(i); - final WindowState replacing = c.getReplacingWindow(); - if (replacing != null) { - return replacing; - } - } - return null; - } - int getRotationAnimationHint() { if (mActivityRecord != null) { return mActivityRecord.mRotationAnimationHint; @@ -4990,14 +4798,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP boolean clearAnimatingFlags() { boolean didSomething = false; - // We don't want to clear it out for windows that get replaced, because the - // animation depends on the flag to remove the replaced window. - // // We also don't clear the mAnimatingExit flag for windows which have the // mRemoveOnExit flag. This indicates an explicit remove request has been issued // by the client. We should let animation proceed and not clear this flag or // they won't eventually be removed by WindowStateAnimator#finishExit. - if (!mWillReplaceWindow && !mRemoveOnExit) { + if (!mRemoveOnExit) { // Clear mAnimating flag together with mAnimatingExit. When animation // changes from exiting to entering, we need to clear this flag until the // new animation gets applied, so that isAnimationStarting() becomes true @@ -5312,7 +5117,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return activity.needsZBoost(); } } - return mWillReplaceWindow; + return false; } private boolean isStartingWindowAssociatedToTask() { diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index e8625bc3d64b..3aac816fcd7a 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -449,7 +449,6 @@ class WindowStateAnimator { if (prepared && mDrawState == HAS_DRAWN) { if (mLastHidden) { mSurfaceController.showRobustly(t); - mAnimator.requestRemovalOfReplacedWindows(w); mLastHidden = false; final DisplayContent displayContent = w.getDisplayContent(); if (!displayContent.getLastHasContent()) { @@ -504,13 +503,6 @@ class WindowStateAnimator { } void applyEnterAnimationLocked() { - // If we are the new part of a window replacement transition and we have requested - // not to animate, we instead want to make it seamless, so we don't want to apply - // an enter transition. - if (mWin.mSkipEnterAnimationForSeamlessReplacement) { - return; - } - final int transit; if (mEnterAnimationPending) { mEnterAnimationPending = false; diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index c6b78982169c..327483ebbef7 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -315,17 +315,6 @@ class WindowToken extends WindowContainer<WindowState> { return mChildren.isEmpty(); } - WindowState getReplacingWindow() { - for (int i = mChildren.size() - 1; i >= 0; i--) { - final WindowState win = mChildren.get(i); - final WindowState replacing = win.getReplacingWindow(); - if (replacing != null) { - return replacing; - } - } - return null; - } - /** Return true if this token has a window that wants the wallpaper displayed behind it. */ boolean windowsCanBeWallpaperTarget() { for (int j = mChildren.size() - 1; j >= 0; j--) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index a1789b2b125d..720533f6a389 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -95,6 +95,7 @@ import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDG import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY; import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE; +import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_FINANCING_STATE_CHANGED; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED; import static android.app.admin.DevicePolicyManager.ACTION_MANAGED_PROFILE_PROVISIONED; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE; @@ -15440,7 +15441,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Slogf.i(LOG_TAG, "Sending %s broadcast to manifest receivers.", intent.getAction()); broadcastIntentToCrossProfileManifestReceivers( intent, parentHandle, requiresPermission); - broadcastIntentToDevicePolicyManagerRoleHolder(intent, parentHandle); + broadcastExplicitIntentToRoleHolder( + intent, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, parentHandle); } @Override @@ -15481,36 +15483,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - private void broadcastIntentToDevicePolicyManagerRoleHolder( - Intent intent, UserHandle userHandle) { - final int userId = userHandle.getIdentifier(); - final String packageName = getDevicePolicyManagementRoleHolderPackageName(mContext); - if (packageName == null) { - return; - } - try { - final Intent packageIntent = new Intent(intent) - .setPackage(packageName); - final List<ResolveInfo> receivers = mIPackageManager.queryIntentReceivers( - packageIntent, - /* resolvedType= */ null, - STOCK_PM_FLAGS, - userId).getList(); - if (receivers.isEmpty()) { - return; - } - for (ResolveInfo receiver : receivers) { - final Intent componentIntent = new Intent(packageIntent) - .setComponent(receiver.getComponentInfo().getComponentName()) - .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); - mContext.sendBroadcastAsUser(componentIntent, userHandle); - } - } catch (RemoteException ex) { - Slogf.w(LOG_TAG, "Cannot get list of broadcast receivers for %s because: %s.", - intent.getAction(), ex); - } - } - /** * Checks whether the package {@code packageName} has the {@code MODIFY_QUIET_MODE} * permission granted for the user {@code userId}. @@ -20718,7 +20690,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private void maybeInstallDevicePolicyManagementRoleHolderInUser(int targetUserId) { String devicePolicyManagerRoleHolderPackageName = - getDevicePolicyManagementRoleHolderPackageName(mContext); + getRoleHolderPackageName(mContext, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT); if (devicePolicyManagerRoleHolderPackageName == null) { Slogf.d(LOG_TAG, "No device policy management role holder specified."); return; @@ -20744,14 +20716,24 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + /** + * If multiple packages hold the role, returns the first package in the list. + */ + @Nullable + private String getRoleHolderPackageName(Context context, String role) { + return getRoleHolderPackageNameOnUser(context, role, Process.myUserHandle()); + } - private String getDevicePolicyManagementRoleHolderPackageName(Context context) { + /** + * If multiple packages hold the role, returns the first package in the list. + */ + @Nullable + private String getRoleHolderPackageNameOnUser(Context context, String role, UserHandle user) { RoleManager roleManager = context.getSystemService(RoleManager.class); // Calling identity needs to be cleared as this method is used in the permissions checks. return mInjector.binderWithCleanCallingIdentity(() -> { - List<String> roleHolders = - roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT); + List<String> roleHolders = roleManager.getRoleHoldersAsUser(role, user); if (roleHolders.isEmpty()) { return null; } @@ -20762,7 +20744,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private boolean isCallerDevicePolicyManagementRoleHolder(CallerIdentity caller) { int callerUid = caller.getUid(); String devicePolicyManagementRoleHolderPackageName = - getDevicePolicyManagementRoleHolderPackageName(mContext); + getRoleHolderPackageName(mContext, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT); int roleHolderUid = mInjector.getPackageManagerInternal().getPackageUid( devicePolicyManagementRoleHolderPackageName, 0, caller.getUserId()); @@ -21830,15 +21812,23 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void register() { - mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.SYSTEM); + mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL); } @Override public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) { - if (!RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT.equals(roleName)) { + if (RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT.equals(roleName)) { + handleDevicePolicyManagementRoleChange(user); + return; + } + if (RoleManager.ROLE_FINANCED_DEVICE_KIOSK.equals(roleName)) { + handleFinancedDeviceKioskRoleChange(); return; } - String newRoleHolder = getRoleHolder(); + } + + private void handleDevicePolicyManagementRoleChange(UserHandle user) { + String newRoleHolder = getDeviceManagementRoleHolder(user); if (isDefaultRoleHolder(newRoleHolder)) { Slogf.i(LOG_TAG, "onRoleHoldersChanged: Default role holder is set, returning early"); @@ -21873,9 +21863,42 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - private String getRoleHolder() { - return DevicePolicyManagerService.this.getDevicePolicyManagementRoleHolderPackageName( - mContext); + private void handleFinancedDeviceKioskRoleChange() { + if (!isDevicePolicyEngineEnabled()) { + return; + } + Slog.i(LOG_TAG, "Handling action " + ACTION_DEVICE_FINANCING_STATE_CHANGED); + Intent intent = new Intent(ACTION_DEVICE_FINANCING_STATE_CHANGED); + mInjector.binderWithCleanCallingIdentity(() -> { + for (UserInfo userInfo : mUserManager.getUsers()) { + UserHandle user = userInfo.getUserHandle(); + broadcastExplicitIntentToRoleHolder( + intent, RoleManager.ROLE_SYSTEM_SUPERVISION, user); + broadcastExplicitIntentToRoleHolder( + intent, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, user); + ActiveAdmin admin = getDeviceOrProfileOwnerAdminLocked(user.getIdentifier()); + if (admin == null) { + continue; + } + if (!isProfileOwnerOfOrganizationOwnedDevice( + admin.info.getComponent(), user.getIdentifier()) + && !isDeviceOwner(admin)) { + continue; + } + // Don't send the broadcast twice if the DPC is the same package as the + // DMRH + if (admin.info.getPackageName().equals(getDeviceManagementRoleHolder(user))) { + continue; + } + broadcastExplicitIntentToPackage( + intent, admin.info.getPackageName(), admin.getUserHandle()); + } + }); + } + + private String getDeviceManagementRoleHolder(UserHandle user) { + return DevicePolicyManagerService.this.getRoleHolderPackageNameOnUser( + mContext, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, user); } private boolean isDefaultRoleHolder(String packageName) { @@ -21935,6 +21958,40 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private void broadcastExplicitIntentToRoleHolder( + Intent intent, String role, UserHandle userHandle) { + String packageName = getRoleHolderPackageNameOnUser(mContext, role, userHandle); + if (packageName == null) { + return; + } + broadcastExplicitIntentToPackage(intent, packageName, userHandle); + } + + private void broadcastExplicitIntentToPackage( + Intent intent, String packageName, UserHandle userHandle) { + int userId = userHandle.getIdentifier(); + if (packageName == null) { + return; + } + Intent packageIntent = new Intent(intent) + .setPackage(packageName); + List<ResolveInfo> receivers = mContext.getPackageManager().queryBroadcastReceiversAsUser( + packageIntent, + PackageManager.ResolveInfoFlags.of(PackageManager.GET_RECEIVERS), + userId); + if (receivers.isEmpty()) { + Slog.i(LOG_TAG, "Found no receivers to handle intent " + intent + + " in package " + packageName); + return; + } + for (ResolveInfo receiver : receivers) { + Intent componentIntent = new Intent(packageIntent) + .setComponent(receiver.getComponentInfo().getComponentName()) + .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mContext.sendBroadcastAsUser(componentIntent, userHandle); + } + } + @Override public List<UserHandle> getPolicyManagedProfiles(@NonNull UserHandle user) { Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java index 7e440494a1e4..7d4f87d73507 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java @@ -17,6 +17,7 @@ package com.android.server.inputmethod; import static android.inputmethodservice.InputMethodService.IME_ACTIVE; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE; import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT; import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT; @@ -35,11 +36,16 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; import android.view.inputmethod.InputMethodManager; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.android.internal.inputmethod.InputBindResult; +import com.android.internal.inputmethod.StartInputFlags; +import com.android.internal.inputmethod.StartInputReason; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -60,8 +66,8 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe super.setUp(); mVisibilityApplier = (DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier(); - mInputMethodManagerService.mCurFocusedWindowClient = mock( - InputMethodManagerService.ClientState.class); + mInputMethodManagerService.setAttachedClientForTesting( + mock(InputMethodManagerService.ClientState.class)); } @Test @@ -119,4 +125,38 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME_IMPLICIT); verifyShowSoftInput(true, true, InputMethodManager.SHOW_IMPLICIT); } + + @Test + public void testApplyImeVisibility_hideImeFromTargetOnSecondaryDisplay() { + // Init a IME target client on the secondary display to show IME. + mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection, + 10 /* selfReportedDisplayId */); + mInputMethodManagerService.setAttachedClientForTesting(null); + startInputOrWindowGainedFocus(mWindowToken, SOFT_INPUT_STATE_ALWAYS_VISIBLE); + + synchronized (ImfLock.class) { + final int displayIdToShowIme = mInputMethodManagerService.getDisplayIdToShowImeLocked(); + // Verify hideIme will apply the expected displayId when the default IME + // visibility applier app STATE_HIDE_IME. + mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME); + verify(mInputMethodManagerService.mWindowManagerInternal).hideIme( + eq(mWindowToken), eq(displayIdToShowIme), eq(null)); + } + } + + private InputBindResult startInputOrWindowGainedFocus(IBinder windowToken, int softInputMode) { + return mInputMethodManagerService.startInputOrWindowGainedFocus( + StartInputReason.WINDOW_FOCUS_GAIN /* startInputReason */, + mMockInputMethodClient /* client */, + windowToken /* windowToken */, + StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR, + softInputMode /* softInputMode */, + 0 /* windowFlags */, + mEditorInfo /* editorInfo */, + mMockRemoteInputConnection /* inputConnection */, + mMockRemoteAccessibilityInputConnection /* remoteAccessibilityInputConnection */, + mTargetSdkVersion /* unverifiedTargetSdkVersion */, + mCallingUserId /* userId */, + mMockImeOnBackInvokedDispatcher /* imeDispatcher */); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java index 5377ee71f480..3a47b476a131 100644 --- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java @@ -50,7 +50,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; @@ -624,8 +623,7 @@ public class DeviceIdleControllerTest { mDeviceIdleController.becomeInactiveIfAppropriateLocked(); verifyStateConditions(STATE_INACTIVE); - verify(mDeviceIdleController) - .scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT), eq(false)); + verify(mDeviceIdleController).scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT)); } @Test @@ -643,8 +641,7 @@ public class DeviceIdleControllerTest { mDeviceIdleController.becomeInactiveIfAppropriateLocked(); verifyStateConditions(STATE_INACTIVE); - verify(mDeviceIdleController) - .scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT), eq(false)); + verify(mDeviceIdleController).scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT)); // The device configuration doesn't require a motion sensor to proceed with idling. // This should be the case on TVs or other such devices. We should set an alarm to move // forward if the motion sensor is missing in this case. @@ -669,8 +666,7 @@ public class DeviceIdleControllerTest { mDeviceIdleController.becomeInactiveIfAppropriateLocked(); verifyStateConditions(STATE_INACTIVE); - verify(mDeviceIdleController) - .scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT), eq(false)); + verify(mDeviceIdleController).scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT)); // The device configuration requires a motion sensor to proceed with idling, // so we should never set an alarm to move forward if the motion sensor is // missing in this case. @@ -699,7 +695,7 @@ public class DeviceIdleControllerTest { mDeviceIdleController.becomeInactiveIfAppropriateLocked(); verifyStateConditions(STATE_INACTIVE); inOrder.verify(mDeviceIdleController) - .scheduleAlarmLocked(eq(timeUntilAlarm + mConstants.INACTIVE_TIMEOUT), eq(false)); + .scheduleAlarmLocked(eq(timeUntilAlarm + mConstants.INACTIVE_TIMEOUT)); enterDeepState(STATE_ACTIVE); setQuickDozeEnabled(true); @@ -709,7 +705,7 @@ public class DeviceIdleControllerTest { mDeviceIdleController.becomeInactiveIfAppropriateLocked(); verifyStateConditions(STATE_QUICK_DOZE_DELAY); inOrder.verify(mDeviceIdleController).scheduleAlarmLocked( - eq(timeUntilAlarm + mConstants.QUICK_DOZE_DELAY_TIMEOUT), eq(false)); + eq(timeUntilAlarm + mConstants.QUICK_DOZE_DELAY_TIMEOUT)); } @Test @@ -736,59 +732,56 @@ public class DeviceIdleControllerTest { setScreenOn(false); verifyStateConditions(STATE_QUICK_DOZE_DELAY); inOrder.verify(mDeviceIdleController) - .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT), eq(false)); + .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT)); enterDeepState(STATE_INACTIVE); setQuickDozeEnabled(true); verifyStateConditions(STATE_QUICK_DOZE_DELAY); inOrder.verify(mDeviceIdleController) - .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT), eq(false)); + .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT)); enterDeepState(STATE_IDLE_PENDING); setQuickDozeEnabled(true); verifyStateConditions(STATE_QUICK_DOZE_DELAY); inOrder.verify(mDeviceIdleController) - .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT), eq(false)); + .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT)); enterDeepState(STATE_SENSING); setQuickDozeEnabled(true); verifyStateConditions(STATE_QUICK_DOZE_DELAY); inOrder.verify(mDeviceIdleController) - .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT), eq(false)); + .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT)); enterDeepState(STATE_LOCATING); setQuickDozeEnabled(true); verifyStateConditions(STATE_QUICK_DOZE_DELAY); inOrder.verify(mDeviceIdleController) - .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT), eq(false)); + .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT)); // IDLE should stay as IDLE. enterDeepState(STATE_IDLE); // Clear out any alarm setting from the order before checking for this section. - inOrder.verify(mDeviceIdleController, atLeastOnce()) - .scheduleAlarmLocked(anyLong(), anyBoolean()); + inOrder.verify(mDeviceIdleController, atLeastOnce()).scheduleAlarmLocked(anyLong()); setQuickDozeEnabled(true); verifyStateConditions(STATE_IDLE); - inOrder.verify(mDeviceIdleController, never()).scheduleAlarmLocked(anyLong(), anyBoolean()); + inOrder.verify(mDeviceIdleController, never()).scheduleAlarmLocked(anyLong()); // IDLE_MAINTENANCE should stay as IDLE_MAINTENANCE. enterDeepState(STATE_IDLE_MAINTENANCE); // Clear out any alarm setting from the order before checking for this section. - inOrder.verify(mDeviceIdleController, atLeastOnce()) - .scheduleAlarmLocked(anyLong(), anyBoolean()); + inOrder.verify(mDeviceIdleController, atLeastOnce()).scheduleAlarmLocked(anyLong()); setQuickDozeEnabled(true); verifyStateConditions(STATE_IDLE_MAINTENANCE); - inOrder.verify(mDeviceIdleController, never()).scheduleAlarmLocked(anyLong(), anyBoolean()); + inOrder.verify(mDeviceIdleController, never()).scheduleAlarmLocked(anyLong()); // State is already QUICK_DOZE_DELAY. No work should be done. enterDeepState(STATE_QUICK_DOZE_DELAY); // Clear out any alarm setting from the order before checking for this section. - inOrder.verify(mDeviceIdleController, atLeastOnce()) - .scheduleAlarmLocked(anyLong(), anyBoolean()); + inOrder.verify(mDeviceIdleController, atLeastOnce()).scheduleAlarmLocked(anyLong()); setQuickDozeEnabled(true); mDeviceIdleController.becomeInactiveIfAppropriateLocked(); verifyStateConditions(STATE_QUICK_DOZE_DELAY); - inOrder.verify(mDeviceIdleController, never()).scheduleAlarmLocked(anyLong(), anyBoolean()); + inOrder.verify(mDeviceIdleController, never()).scheduleAlarmLocked(anyLong()); } @Test @@ -2685,17 +2678,12 @@ public class DeviceIdleControllerTest { if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) { enterDeepState(STATE_IDLE); long now = SystemClock.elapsedRealtime(); - long alarm = mDeviceIdleController.getNextAlarmTime(); mDeviceIdleController.setIdleStartTimeForTest( now - (long) (mConstants.IDLE_TIMEOUT * 0.6)); - long newAlarm = mDeviceIdleController.getNextAlarmTime(); - assertTrue("maintenance not reschedule IDLE_TIMEOUT * 0.6", - newAlarm == alarm); + verifyStateConditions(STATE_IDLE); mDeviceIdleController.setIdleStartTimeForTest( now - (long) (mConstants.IDLE_TIMEOUT * 1.2)); - newAlarm = mDeviceIdleController.getNextAlarmTime(); - assertTrue("maintenance not reschedule IDLE_TIMEOUT * 1.2", - (newAlarm - now) < minuteInMillis); + verifyStateConditions(STATE_IDLE_MAINTENANCE); mDeviceIdleController.resetPreIdleTimeoutMode(); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java index 7b55edb31df3..e056417811c7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java @@ -50,6 +50,7 @@ import android.app.UiModeManager; import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; +import android.app.job.JobWorkItem; import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; import android.content.Context; @@ -91,6 +92,7 @@ import java.time.ZoneOffset; public class JobSchedulerServiceTest { private static final String TAG = JobSchedulerServiceTest.class.getSimpleName(); + private static final int TEST_UID = 10123; private JobSchedulerService mService; @@ -177,6 +179,9 @@ public class JobSchedulerServiceTest { if (mMockingSession != null) { mMockingSession.finishMocking(); } + mService.cancelJobsForUid(TEST_UID, true, + JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_UNKNOWN, + "test cleanup"); } private Clock getAdvancedClock(Clock clock, long incrementMs) { @@ -1170,7 +1175,7 @@ public class JobSchedulerServiceTest { i < 300 ? JobScheduler.RESULT_SUCCESS : JobScheduler.RESULT_FAILURE; assertEquals("Got unexpected result for schedule #" + (i + 1), expected, - mService.scheduleAsPackage(job, null, 10123, null, 0, "JSSTest", "")); + mService.scheduleAsPackage(job, null, TEST_UID, null, 0, "JSSTest", "")); } } @@ -1191,7 +1196,7 @@ public class JobSchedulerServiceTest { for (int i = 0; i < 500; ++i) { assertEquals("Got unexpected result for schedule #" + (i + 1), JobScheduler.RESULT_SUCCESS, - mService.scheduleAsPackage(job, null, 10123, null, 0, "JSSTest", "")); + mService.scheduleAsPackage(job, null, TEST_UID, null, 0, "JSSTest", "")); } } @@ -1212,7 +1217,7 @@ public class JobSchedulerServiceTest { for (int i = 0; i < 500; ++i) { assertEquals("Got unexpected result for schedule #" + (i + 1), JobScheduler.RESULT_SUCCESS, - mService.scheduleAsPackage(job, null, 10123, "proxied.package", 0, "JSSTest", + mService.scheduleAsPackage(job, null, TEST_UID, "proxied.package", 0, "JSSTest", "")); } } @@ -1236,11 +1241,63 @@ public class JobSchedulerServiceTest { i < 300 ? JobScheduler.RESULT_SUCCESS : JobScheduler.RESULT_FAILURE; assertEquals("Got unexpected result for schedule #" + (i + 1), expected, - mService.scheduleAsPackage(job, null, 10123, job.getService().getPackageName(), + mService.scheduleAsPackage(job, null, TEST_UID, + job.getService().getPackageName(), 0, "JSSTest", "")); } } + /** + * Tests that the number of persisted JobWorkItems is capped. + */ + @Test + public void testScheduleLimiting_JobWorkItems_Nonpersisted() { + mService.mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 500; + mService.mConstants.ENABLE_API_QUOTAS = false; + mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false; + mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false; + mService.updateQuotaTracker(); + + final JobInfo job = createJobInfo().setPersisted(false).build(); + final JobWorkItem item = new JobWorkItem.Builder().build(); + for (int i = 0; i < 1000; ++i) { + assertEquals("Got unexpected result for schedule #" + (i + 1), + JobScheduler.RESULT_SUCCESS, + mService.scheduleAsPackage(job, item, TEST_UID, + job.getService().getPackageName(), + 0, "JSSTest", "")); + } + } + + /** + * Tests that the number of persisted JobWorkItems is capped. + */ + @Test + public void testScheduleLimiting_JobWorkItems_Persisted() { + mService.mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 500; + mService.mConstants.ENABLE_API_QUOTAS = false; + mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false; + mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false; + mService.updateQuotaTracker(); + + final JobInfo job = createJobInfo().setPersisted(true).build(); + final JobWorkItem item = new JobWorkItem.Builder().build(); + for (int i = 0; i < 500; ++i) { + assertEquals("Got unexpected result for schedule #" + (i + 1), + JobScheduler.RESULT_SUCCESS, + mService.scheduleAsPackage(job, item, TEST_UID, + job.getService().getPackageName(), + 0, "JSSTest", "")); + } + try { + mService.scheduleAsPackage(job, item, TEST_UID, job.getService().getPackageName(), + 0, "JSSTest", ""); + fail("Added more items than allowed"); + } catch (IllegalStateException expected) { + // Success + } + } + /** Tests that jobs are removed from the pending list if the user stops the app. */ @Test public void testUserStopRemovesPending() { diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java index 71280ce01779..e81b63c8c9b1 100644 --- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java @@ -403,6 +403,7 @@ public class ScribeTest { ApplicationInfo applicationInfo = new ApplicationInfo(); applicationInfo.uid = UserHandle.getUid(userId, Math.abs(pkgName.hashCode())); pkgInfo.applicationInfo = applicationInfo; - mInstalledPackages.add(userId, pkgName, new InstalledPackageInfo(getContext(), pkgInfo)); + mInstalledPackages.add(userId, pkgName, new InstalledPackageInfo(getContext(), userId, + pkgInfo)); } } diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java index 8196d6a35cbd..e396263b1679 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java @@ -52,7 +52,7 @@ public class DeviceStateNotificationControllerTest { private static final int STATE_WITHOUT_NOTIFICATION = 1; private static final int STATE_WITH_ACTIVE_NOTIFICATION = 2; - private static final int STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION = 3; + private static final int STATE_WITH_ALL_NOTIFICATION = 3; private static final int VALID_APP_UID = 1000; private static final int INVALID_APP_UID = 2000; @@ -68,6 +68,8 @@ public class DeviceStateNotificationControllerTest { private static final String CONTENT_2 = "content2:%1$s"; private static final String THERMAL_TITLE_2 = "thermal_title2"; private static final String THERMAL_CONTENT_2 = "thermal_content2"; + private static final String POWER_SAVE_TITLE_2 = "power_save_title2"; + private static final String POWER_SAVE_CONTENT_2 = "power_save_content2"; private DeviceStateNotificationController mController; @@ -88,11 +90,12 @@ public class DeviceStateNotificationControllerTest { notificationInfos.put(STATE_WITH_ACTIVE_NOTIFICATION, new DeviceStateNotificationController.NotificationInfo( NAME_1, TITLE_1, CONTENT_1, - "", "")); - notificationInfos.put(STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION, + "", "", "", "")); + notificationInfos.put(STATE_WITH_ALL_NOTIFICATION, new DeviceStateNotificationController.NotificationInfo( NAME_2, TITLE_2, CONTENT_2, - THERMAL_TITLE_2, THERMAL_CONTENT_2)); + THERMAL_TITLE_2, THERMAL_CONTENT_2, + POWER_SAVE_TITLE_2, POWER_SAVE_CONTENT_2)); when(packageManager.getNameForUid(VALID_APP_UID)).thenReturn(VALID_APP_NAME); when(packageManager.getNameForUid(INVALID_APP_UID)).thenReturn(INVALID_APP_NAME); @@ -139,10 +142,46 @@ public class DeviceStateNotificationControllerTest { } @Test + public void test_powerSaveNotification() { + // Verify that the active notification is created. + mController.showStateActiveNotificationIfNeeded( + STATE_WITH_ALL_NOTIFICATION, VALID_APP_UID); + verify(mNotificationManager).notify( + eq(DeviceStateNotificationController.NOTIFICATION_TAG), + eq(DeviceStateNotificationController.NOTIFICATION_ID), + mNotificationCaptor.capture()); + Notification notification = mNotificationCaptor.getValue(); + assertEquals(TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE)); + assertEquals(String.format(CONTENT_2, VALID_APP_LABEL), + notification.extras.getString(Notification.EXTRA_TEXT)); + assertEquals(Notification.FLAG_ONGOING_EVENT, + notification.flags & Notification.FLAG_ONGOING_EVENT); + Mockito.clearInvocations(mNotificationManager); + + // Verify that the thermal critical notification is created. + mController.showPowerSaveNotificationIfNeeded( + STATE_WITH_ALL_NOTIFICATION); + verify(mNotificationManager).notify( + eq(DeviceStateNotificationController.NOTIFICATION_TAG), + eq(DeviceStateNotificationController.NOTIFICATION_ID), + mNotificationCaptor.capture()); + notification = mNotificationCaptor.getValue(); + assertEquals(POWER_SAVE_TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE)); + assertEquals(POWER_SAVE_CONTENT_2, notification.extras.getString(Notification.EXTRA_TEXT)); + assertEquals(0, notification.flags & Notification.FLAG_ONGOING_EVENT); + + // Verify that the notification is canceled. + mController.cancelNotification(STATE_WITH_ALL_NOTIFICATION); + verify(mNotificationManager).cancel( + DeviceStateNotificationController.NOTIFICATION_TAG, + DeviceStateNotificationController.NOTIFICATION_ID); + } + + @Test public void test_thermalNotification() { // Verify that the active notification is created. mController.showStateActiveNotificationIfNeeded( - STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION, VALID_APP_UID); + STATE_WITH_ALL_NOTIFICATION, VALID_APP_UID); verify(mNotificationManager).notify( eq(DeviceStateNotificationController.NOTIFICATION_TAG), eq(DeviceStateNotificationController.NOTIFICATION_ID), @@ -157,7 +196,7 @@ public class DeviceStateNotificationControllerTest { // Verify that the thermal critical notification is created. mController.showThermalCriticalNotificationIfNeeded( - STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION); + STATE_WITH_ALL_NOTIFICATION); verify(mNotificationManager).notify( eq(DeviceStateNotificationController.NOTIFICATION_TAG), eq(DeviceStateNotificationController.NOTIFICATION_ID), @@ -168,7 +207,7 @@ public class DeviceStateNotificationControllerTest { assertEquals(0, notification.flags & Notification.FLAG_ONGOING_EVENT); // Verify that the notification is canceled. - mController.cancelNotification(STATE_WITH_ACTIVE_NOTIFICATION); + mController.cancelNotification(STATE_WITH_ALL_NOTIFICATION); verify(mNotificationManager).cancel( DeviceStateNotificationController.NOTIFICATION_TAG, DeviceStateNotificationController.NOTIFICATION_ID); diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java index 7125796e7b98..7e40f96154d2 100644 --- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java @@ -20,6 +20,8 @@ package com.android.server.policy; import static android.content.Context.SENSOR_SERVICE; import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED; +import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED; +import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED; import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL; import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL; import static com.android.server.policy.DeviceStateProviderImpl.DEFAULT_DEVICE_STATE; @@ -327,7 +329,8 @@ public final class DeviceStateProviderImplTest { + " <name>THERMAL_TEST</name>\n" + " <flags>\n" + " <flag>FLAG_EMULATED_ONLY</flag>\n" - + " <flag>FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL</flag>\n" + + " <flag>FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL</flag>\n" + + " <flag>FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE</flag>\n" + " </flags>\n" + " </device-state>\n" + "</device-state-config>\n"; @@ -354,7 +357,8 @@ public final class DeviceStateProviderImplTest { new DeviceState(3, "OPENED", 0 /* flags */), new DeviceState(4, "THERMAL_TEST", DeviceState.FLAG_EMULATED_ONLY - | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) }, + | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL + | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) }, mDeviceStateArrayCaptor.getValue()); // onStateChanged() should not be called because the provider has not yet been notified of // the initial sensor state. @@ -419,7 +423,8 @@ public final class DeviceStateProviderImplTest { new DeviceState(3, "OPENED", 0 /* flags */), new DeviceState(4, "THERMAL_TEST", DeviceState.FLAG_EMULATED_ONLY - | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) }, + | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL + | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) }, mDeviceStateArrayCaptor.getValue()); Mockito.clearInvocations(listener); @@ -451,7 +456,65 @@ public final class DeviceStateProviderImplTest { new DeviceState(3, "OPENED", 0 /* flags */), new DeviceState(4, "THERMAL_TEST", DeviceState.FLAG_EMULATED_ONLY - | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) }, + | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL + | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) }, + mDeviceStateArrayCaptor.getValue()); + } + + @Test + public void test_flagDisableWhenPowerSaveEnabled() throws Exception { + Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE); + when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor)); + DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor); + + provider.onPowerSaveModeChanged(false /* isPowerSaveModeEnabled */); + DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class); + provider.setListener(listener); + + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED)); + assertArrayEquals( + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */), + new DeviceState(3, "OPENED", 0 /* flags */), + new DeviceState(4, "THERMAL_TEST", + DeviceState.FLAG_EMULATED_ONLY + | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL + | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) }, + mDeviceStateArrayCaptor.getValue()); + Mockito.clearInvocations(listener); + + provider.onPowerSaveModeChanged(false /* isPowerSaveModeEnabled */); + verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED)); + Mockito.clearInvocations(listener); + + // The THERMAL_TEST state should be disabled due to power save being enabled. + provider.onPowerSaveModeChanged(true /* isPowerSaveModeEnabled */); + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED)); + assertArrayEquals( + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */), + new DeviceState(3, "OPENED", 0 /* flags */) }, + mDeviceStateArrayCaptor.getValue()); + Mockito.clearInvocations(listener); + + // The THERMAL_TEST state should be re-enabled. + provider.onPowerSaveModeChanged(false /* isPowerSaveModeEnabled */); + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED)); + assertArrayEquals( + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */), + new DeviceState(3, "OPENED", 0 /* flags */), + new DeviceState(4, "THERMAL_TEST", + DeviceState.FLAG_EMULATED_ONLY + | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL + | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) }, mDeviceStateArrayCaptor.getValue()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index c73237efa93e..0ae579bab413 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -376,46 +376,6 @@ public class AppTransitionControllerTest extends WindowTestsBase { } @Test - public void testGetAnimationTargets_windowsAreBeingReplaced() { - // [DisplayContent] -+- [Task1] - [ActivityRecord1] (opening, visible) - // +- [AppWindow1] (being-replaced) - // +- [Task2] - [ActivityRecord2] (closing, invisible) - // +- [AppWindow2] (being-replaced) - final ActivityRecord activity1 = createActivityRecord(mDisplayContent); - final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams( - TYPE_BASE_APPLICATION); - attrs.setTitle("AppWindow1"); - final TestWindowState appWindow1 = createWindowState(attrs, activity1); - appWindow1.mWillReplaceWindow = true; - activity1.addWindow(appWindow1); - - final ActivityRecord activity2 = createActivityRecord(mDisplayContent); - activity2.setVisible(false); - activity2.setVisibleRequested(false); - attrs.setTitle("AppWindow2"); - final TestWindowState appWindow2 = createWindowState(attrs, activity2); - appWindow2.mWillReplaceWindow = true; - activity2.addWindow(appWindow2); - - final ArraySet<ActivityRecord> opening = new ArraySet<>(); - opening.add(activity1); - final ArraySet<ActivityRecord> closing = new ArraySet<>(); - closing.add(activity2); - - // Animate opening apps even if it's already visible in case its windows are being replaced. - // Don't animate closing apps if it's already invisible even though its windows are being - // replaced. - assertEquals( - new ArraySet<>(new WindowContainer[]{activity1.getRootTask()}), - AppTransitionController.getAnimationTargets( - opening, closing, true /* visible */)); - assertEquals( - new ArraySet<>(new WindowContainer[]{}), - AppTransitionController.getAnimationTargets( - opening, closing, false /* visible */)); - } - - @Test public void testGetAnimationTargets_openingClosingInDifferentTask() { // [DisplayContent] -+- [Task1] -+- [ActivityRecord1] (opening, invisible) // | +- [ActivityRecord2] (invisible) diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java index 2d382a27d747..79046db04d1b 100644 --- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java +++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java @@ -37,6 +37,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; @@ -160,11 +161,20 @@ final class TranslationManagerServiceImpl extends return null; } final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName); - if (!isServiceAvailableForUser(serviceComponent)) { + boolean isServiceAvailableForUser; + final long identity = Binder.clearCallingIdentity(); + try { + isServiceAvailableForUser = isServiceAvailableForUser(serviceComponent); if (mMaster.verbose) { - Slog.v(TAG, "ensureRemoteServiceLocked(): " + serviceComponent - + " is not available,"); + Slog.v(TAG, "ensureRemoteServiceLocked(): isServiceAvailableForUser=" + + isServiceAvailableForUser); } + } finally { + Binder.restoreCallingIdentity(identity); + } + if (!isServiceAvailableForUser) { + Slog.w(TAG, "ensureRemoteServiceLocked(): " + serviceComponent + + " is not available,"); return null; } mRemoteTranslationService = new RemoteTranslationService(getContext(), serviceComponent, diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java index 06fc41626e50..dbc824c384a2 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java @@ -68,6 +68,7 @@ import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteException; import android.os.SharedMemory; +import android.service.voice.DetectorFailure; import android.service.voice.HotwordDetectedResult; import android.service.voice.HotwordDetectionService; import android.service.voice.HotwordDetectionServiceFailure; @@ -623,11 +624,9 @@ abstract class DetectorSession { mRemoteDetectionService = remoteDetectionService; } - void reportErrorLocked(int errorCode, @NonNull String errorMessage) { + void reportErrorLocked(@NonNull DetectorFailure detectorFailure) { try { - // TODO: Use instanceof(this) to get different detector to set the right error source. - mCallback.onDetectionFailure( - new HotwordDetectionServiceFailure(errorCode, errorMessage)); + mCallback.onDetectionFailure(detectorFailure); } catch (RemoteException e) { Slog.w(TAG, "Failed to report onError status: " + e); if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 025e1dc78c86..4fd5979b3d9f 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -51,11 +51,14 @@ import android.os.ServiceManager; import android.os.SharedMemory; import android.provider.DeviceConfig; import android.service.voice.HotwordDetectionService; +import android.service.voice.HotwordDetectionServiceFailure; import android.service.voice.HotwordDetector; import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; import android.service.voice.ISandboxedDetectionService; import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback; +import android.service.voice.UnknownFailure; import android.service.voice.VisualQueryDetectionService; +import android.service.voice.VisualQueryDetectionServiceFailure; import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity; import android.speech.IRecognitionServiceManager; import android.util.Slog; @@ -109,6 +112,16 @@ final class HotwordDetectionConnection { private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour private static final int MAX_ISOLATED_PROCESS_NUMBER = 10; + /** + * Indicates the {@link HotwordDetectionService} is created. + */ + private static final int DETECTION_SERVICE_TYPE_HOTWORD = 1; + + /** + * Indicates the {@link VisualQueryDetectionService} is created. + */ + private static final int DETECTION_SERVICE_TYPE_VISUAL_QUERY = 2; + // TODO: This may need to be a Handler(looper) private final ScheduledExecutorService mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); @@ -186,11 +199,11 @@ final class HotwordDetectionConnection { mHotwordDetectionServiceConnectionFactory = new ServiceConnectionFactory(hotwordDetectionServiceIntent, - bindInstantServiceAllowed); + bindInstantServiceAllowed, DETECTION_SERVICE_TYPE_HOTWORD); mVisualQueryDetectionServiceConnectionFactory = new ServiceConnectionFactory(visualQueryDetectionServiceIntent, - bindInstantServiceAllowed); + bindInstantServiceAllowed, DETECTION_SERVICE_TYPE_VISUAL_QUERY); mLastRestartInstant = Instant.now(); @@ -604,17 +617,20 @@ final class HotwordDetectionConnection { private class ServiceConnectionFactory { private final Intent mIntent; private final int mBindingFlags; + private final int mDetectionServiceType; - ServiceConnectionFactory(@NonNull Intent intent, boolean bindInstantServiceAllowed) { + ServiceConnectionFactory(@NonNull Intent intent, boolean bindInstantServiceAllowed, + int detectionServiceType) { mIntent = intent; mBindingFlags = bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0; + mDetectionServiceType = detectionServiceType; } ServiceConnection createLocked() { ServiceConnection connection = new ServiceConnection(mContext, mIntent, mBindingFlags, mUser, ISandboxedDetectionService.Stub::asInterface, - mRestartCount % MAX_ISOLATED_PROCESS_NUMBER); + mRestartCount % MAX_ISOLATED_PROCESS_NUMBER, mDetectionServiceType); connection.connect(); updateAudioFlinger(connection, mAudioFlinger); @@ -635,15 +651,17 @@ final class HotwordDetectionConnection { private boolean mRespectServiceConnectionStatusChanged = true; private boolean mIsBound = false; private boolean mIsLoggedFirstConnect = false; + private final int mDetectionServiceType; ServiceConnection(@NonNull Context context, @NonNull Intent serviceIntent, int bindingFlags, int userId, @Nullable Function<IBinder, ISandboxedDetectionService> binderAsInterface, - int instanceNumber) { + int instanceNumber, int detectionServiceType) { super(context, serviceIntent, bindingFlags, userId, binderAsInterface); this.mIntent = serviceIntent; this.mBindingFlags = bindingFlags; this.mInstanceNumber = instanceNumber; + this.mDetectionServiceType = detectionServiceType; } @Override // from ServiceConnector.Impl @@ -660,14 +678,14 @@ final class HotwordDetectionConnection { mIsBound = connected; if (!connected) { - if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { + if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) { HotwordMetricsLogger.writeDetectorEvent(mDetectorType, HOTWORD_DETECTOR_EVENTS__EVENT__ON_DISCONNECTED, mVoiceInteractionServiceUid); } } else if (!mIsLoggedFirstConnect) { mIsLoggedFirstConnect = true; - if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { + if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) { HotwordMetricsLogger.writeDetectorEvent(mDetectorType, HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED, mVoiceInteractionServiceUid); @@ -684,7 +702,7 @@ final class HotwordDetectionConnection { @Override public void binderDied() { super.binderDied(); - Slog.w(TAG, "binderDied"); + Slog.w(TAG, "binderDied mDetectionServiceType = " + mDetectionServiceType); synchronized (mLock) { if (!mRespectServiceConnectionStatusChanged) { Slog.v(TAG, "Ignored #binderDied event"); @@ -693,13 +711,10 @@ final class HotwordDetectionConnection { } //TODO(b265535257): report error to either service only. synchronized (HotwordDetectionConnection.this.mLock) { - runForEachDetectorSessionLocked((session) -> { - session.reportErrorLocked(DetectorSession.HOTWORD_DETECTION_SERVICE_DIED, - "Detection service is dead."); - }); + runForEachDetectorSessionLocked(this::reportBinderDiedLocked); } // Can improve to log exit reason if needed - if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { + if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) { HotwordMetricsLogger.writeKeyphraseTriggerEvent( mDetectorType, HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH, @@ -711,7 +726,7 @@ final class HotwordDetectionConnection { protected boolean bindService( @NonNull android.content.ServiceConnection serviceConnection) { try { - if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { + if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) { HotwordMetricsLogger.writeDetectorEvent(mDetectorType, HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE, mVoiceInteractionServiceUid); @@ -723,7 +738,12 @@ final class HotwordDetectionConnection { mExecutor, serviceConnection); if (!bindResult) { - if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { + Slog.w(TAG, + "bindService failure mDetectionServiceType = " + mDetectionServiceType); + synchronized (HotwordDetectionConnection.this.mLock) { + runForEachDetectorSessionLocked(this::reportBindServiceFailureLocked); + } + if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) { HotwordMetricsLogger.writeDetectorEvent(mDetectorType, HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL, mVoiceInteractionServiceUid); @@ -731,7 +751,7 @@ final class HotwordDetectionConnection { } return bindResult; } catch (IllegalArgumentException e) { - if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { + if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) { HotwordMetricsLogger.writeDetectorEvent(mDetectorType, HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL, mVoiceInteractionServiceUid); @@ -752,6 +772,42 @@ final class HotwordDetectionConnection { mRespectServiceConnectionStatusChanged = false; } } + + private void reportBinderDiedLocked(DetectorSession detectorSession) { + if (mDetectionServiceType == DETECTION_SERVICE_TYPE_HOTWORD && ( + detectorSession instanceof DspTrustedHotwordDetectorSession + || detectorSession instanceof SoftwareTrustedHotwordDetectorSession)) { + detectorSession.reportErrorLocked(new HotwordDetectionServiceFailure( + HotwordDetectionServiceFailure.ERROR_CODE_BINDING_DIED, + "Detection service is dead.")); + } else if (mDetectionServiceType == DETECTION_SERVICE_TYPE_VISUAL_QUERY + && detectorSession instanceof VisualQueryDetectorSession) { + detectorSession.reportErrorLocked(new VisualQueryDetectionServiceFailure( + VisualQueryDetectionServiceFailure.ERROR_CODE_BINDING_DIED, + "Detection service is dead.")); + } else { + detectorSession.reportErrorLocked(new UnknownFailure( + "Detection service is dead with unknown detection service type.")); + } + } + + private void reportBindServiceFailureLocked(DetectorSession detectorSession) { + if (mDetectionServiceType == DETECTION_SERVICE_TYPE_HOTWORD && ( + detectorSession instanceof DspTrustedHotwordDetectorSession + || detectorSession instanceof SoftwareTrustedHotwordDetectorSession)) { + detectorSession.reportErrorLocked(new HotwordDetectionServiceFailure( + HotwordDetectionServiceFailure.ERROR_CODE_BIND_FAILURE, + "Bind detection service failure.")); + } else if (mDetectionServiceType == DETECTION_SERVICE_TYPE_VISUAL_QUERY + && detectorSession instanceof VisualQueryDetectorSession) { + detectorSession.reportErrorLocked(new VisualQueryDetectionServiceFailure( + VisualQueryDetectionServiceFailure.ERROR_CODE_BIND_FAILURE, + "Bind detection service failure.")); + } else { + detectorSession.reportErrorLocked(new UnknownFailure( + "Bind detection service failure with unknown detection service type.")); + } + } } @SuppressWarnings("GuardedBy") diff --git a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl index 24420833bdb6..a81444d51374 100644 --- a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl +++ b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl @@ -24,15 +24,22 @@ import android.telephony.satellite.PointingInfo; */ oneway interface ISatelliteTransmissionUpdateCallback { /** - * Called when satellite datagram transfer state changed. + * Called when satellite datagram send state changed. * - * @param state The new datagram transfer state. + * @param state The new send datagram transfer state. * @param sendPendingCount The number of datagrams that are currently being sent. - * @param receivePendingCount The number of datagrams that are currently being received. * @param errorCode If datagram transfer failed, the reason for failure. */ - void onDatagramTransferStateChanged(in int state, in int sendPendingCount, - in int receivePendingCount, in int errorCode); + void onSendDatagramStateChanged(in int state, in int sendPendingCount, in int errorCode); + + /** + * Called when satellite datagram receive state changed. + * + * @param state The new receive datagram transfer state. + * @param receivePendingCount The number of datagrams that are currently pending to be received. + * @param errorCode If datagram transfer failed, the reason for failure. + */ + void onReceiveDatagramStateChanged(in int state, in int receivePendingCount, in int errorCode); /** * Called when the satellite position changed. diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index d0abfbf80e76..e32566dc470f 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -647,6 +647,7 @@ public class SatelliteManager { }) @Retention(RetentionPolicy.SOURCE) public @interface SatelliteDatagramTransferState {} + // TODO: Split into two enums for sending and receiving states /** * Satellite modem is in idle state. @@ -750,19 +751,27 @@ public class SatelliteManager { }; ISatelliteTransmissionUpdateCallback internalCallback = new ISatelliteTransmissionUpdateCallback.Stub() { + @Override - public void onDatagramTransferStateChanged(int state, - int sendPendingCount, int receivePendingCount, int errorCode) { + public void onSatellitePositionChanged(PointingInfo pointingInfo) { executor.execute(() -> Binder.withCleanCallingIdentity( - () -> callback.onDatagramTransferStateChanged( - state, sendPendingCount, receivePendingCount, - errorCode))); + () -> callback.onSatellitePositionChanged(pointingInfo))); } @Override - public void onSatellitePositionChanged(PointingInfo pointingInfo) { + public void onSendDatagramStateChanged(int state, int sendPendingCount, + int errorCode) { executor.execute(() -> Binder.withCleanCallingIdentity( - () -> callback.onSatellitePositionChanged(pointingInfo))); + () -> callback.onSendDatagramStateChanged( + state, sendPendingCount, errorCode))); + } + + @Override + public void onReceiveDatagramStateChanged(int state, + int receivePendingCount, int errorCode) { + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> callback.onReceiveDatagramStateChanged( + state, receivePendingCount, errorCode))); } }; sSatelliteTransmissionUpdateCallbackMap.put(callback, internalCallback); diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java index 0efbd1fbdfef..d4fe57a0be2e 100644 --- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java @@ -33,14 +33,24 @@ public interface SatelliteTransmissionUpdateCallback { void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo); /** - * Called when satellite datagram transfer state changed. + * Called when satellite datagram send state changed. * - * @param state The new datagram transfer state. + * @param state The new send datagram transfer state. * @param sendPendingCount The number of datagrams that are currently being sent. - * @param receivePendingCount The number of datagrams that are currently being received. * @param errorCode If datagram transfer failed, the reason for failure. */ - void onDatagramTransferStateChanged(@SatelliteManager.SatelliteDatagramTransferState int state, - int sendPendingCount, int receivePendingCount, + void onSendDatagramStateChanged( + @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount, + @SatelliteManager.SatelliteError int errorCode); + + /** + * Called when satellite datagram receive state changed. + * + * @param state The new receive datagram transfer state. + * @param receivePendingCount The number of datagrams that are currently pending to be received. + * @param errorCode If datagram transfer failed, the reason for failure. + */ + void onReceiveDatagramStateChanged( + @SatelliteManager.SatelliteDatagramTransferState int state, int receivePendingCount, @SatelliteManager.SatelliteError int errorCode); } diff --git a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java index d2a6bf288be4..81efda17abe3 100644 --- a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java +++ b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java @@ -16,6 +16,8 @@ package com.android.server.pm; +import static org.junit.Assume.assumeFalse; + import android.app.AlarmManager; import android.content.Context; import android.os.Environment; @@ -112,6 +114,7 @@ public final class BackgroundDexOptServiceIntegrationTests { @Before public void setUp() throws IOException { + assumeFalse(SystemProperties.getBoolean("dalvik.vm.useartservice", false)); File dataDir = getContext().getDataDir(); mBigFile = new File(dataDir, BIG_FILE); } diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp index 83893ba46885..a4c48fd5f342 100644 --- a/tests/Input/Android.bp +++ b/tests/Input/Android.bp @@ -13,6 +13,9 @@ android_test { "src/**/*.java", "src/**/*.kt", ], + kotlincflags: [ + "-Werror", + ], platform_apis: true, certificate: "platform", static_libs: [ diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt index 1d65cc35c3bc..0246426c1d33 100644 --- a/tests/Input/src/com/android/test/input/AnrTest.kt +++ b/tests/Input/src/com/android/test/input/AnrTest.kt @@ -73,7 +73,7 @@ class AnrTest { val contentResolver = instrumentation.targetContext.contentResolver hideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0) Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, 0) - PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage().getName() + PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage()!!.getName() } @After diff --git a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt index d83a4570fedc..3a24406e2b73 100644 --- a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt +++ b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt @@ -45,7 +45,8 @@ class UnresponsiveGestureMonitorActivity : Activity() { private lateinit var mInputMonitor: InputMonitor override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - mInputMonitor = InputManager.getInstance().monitorGestureInput(MONITOR_NAME, displayId) + val inputManager = getSystemService(InputManager::class.java) + mInputMonitor = inputManager.monitorGestureInput(MONITOR_NAME, displayId) mInputEventReceiver = UnresponsiveReceiver( mInputMonitor.getInputChannel(), Looper.myLooper()) } diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt index 423a6843891e..b4f6a1c12d8f 100644 --- a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt @@ -36,7 +36,6 @@ class AndroidFrameworkIssueRegistry : IssueRegistry() { CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED, SaferParcelChecker.ISSUE_UNSAFE_API_USAGE, PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS, - RegisterReceiverFlagDetector.ISSUE_RECEIVER_EXPORTED_FLAG, PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE, PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD, ) diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt deleted file mode 100644 index c3e0428316c3..000000000000 --- a/tools/lint/framework/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt +++ /dev/null @@ -1,927 +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.google.android.lint - -import com.android.tools.lint.checks.DataFlowAnalyzer -import com.android.tools.lint.detector.api.Category -import com.android.tools.lint.detector.api.ConstantEvaluator -import com.android.tools.lint.detector.api.Detector -import com.android.tools.lint.detector.api.Implementation -import com.android.tools.lint.detector.api.Issue -import com.android.tools.lint.detector.api.JavaContext -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 com.android.tools.lint.detector.api.UastLintUtils.Companion.findLastAssignment -import com.android.tools.lint.detector.api.getMethodName -import com.intellij.psi.PsiMethod -import com.intellij.psi.PsiVariable -import org.jetbrains.kotlin.psi.psiUtil.parameterIndex -import org.jetbrains.uast.UCallExpression -import org.jetbrains.uast.UElement -import org.jetbrains.uast.UExpression -import org.jetbrains.uast.UParenthesizedExpression -import org.jetbrains.uast.UQualifiedReferenceExpression -import org.jetbrains.uast.getContainingUMethod -import org.jetbrains.uast.isNullLiteral -import org.jetbrains.uast.skipParenthesizedExprDown -import org.jetbrains.uast.tryResolve - -/** - * Detector that identifies `registerReceiver()` calls which are missing the `RECEIVER_EXPORTED` or - * `RECEIVER_NOT_EXPORTED` flags on T+. - * - * TODO: Add API level conditions to better support non-platform code. - * 1. Check if registerReceiver() call is reachable on T+. - * 2. Check if targetSdkVersion is T+. - * - * eg: isWithinVersionCheckConditional(context, node, 31, false) - * eg: isPrecededByVersionCheckExit(context, node, 31) ? - */ -@Suppress("UnstableApiUsage") -class RegisterReceiverFlagDetector : Detector(), SourceCodeScanner { - - override fun getApplicableMethodNames(): List<String> = listOf( - "registerReceiver", - "registerReceiverAsUser", - "registerReceiverForAllUsers" - ) - - private fun checkIsProtectedReceiverAndReturnUnprotectedActions( - filterArg: UExpression, - node: UCallExpression, - evaluator: ConstantEvaluator - ): Pair<Boolean, List<String>> { // isProtected, unprotectedActions - val actions = mutableSetOf<String>() - val construction = findIntentFilterConstruction(filterArg, node) - - if (construction == null) return Pair(false, listOf<String>()) - val constructorActionArg = construction.getArgumentForParameter(0) - (constructorActionArg?.let(evaluator::evaluate) as? String)?.let(actions::add) - - val actionCollectorVisitor = - ActionCollectorVisitor(setOf(construction), node, evaluator) - - val parent = node.getContainingUMethod() - parent?.accept(actionCollectorVisitor) - actions.addAll(actionCollectorVisitor.actions) - - // If we failed to evaluate any actions, there will be a null action in the set. - val isProtected = - actions.all(PROTECTED_BROADCASTS::contains) && - !actionCollectorVisitor.intentFilterEscapesScope - val unprotectedActionsList = actions.filterNot(PROTECTED_BROADCASTS::contains) - return Pair(isProtected, unprotectedActionsList) - } - - override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { - if (!context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) return - - // The parameter positions vary across the various registerReceiver*() methods, so rather - // than hardcode them we simply look them up based on the parameter name and type. - val receiverArg = - findArgument(node, method, "android.content.BroadcastReceiver", "receiver") - val filterArg = findArgument(node, method, "android.content.IntentFilter", "filter") - val flagsArg = findArgument(node, method, "int", "flags") - - if (receiverArg == null || receiverArg.isNullLiteral() || filterArg == null) { - return - } - - val evaluator = ConstantEvaluator().allowFieldInitializers() - - val (isProtected, unprotectedActionsList) = - checkIsProtectedReceiverAndReturnUnprotectedActions(filterArg, node, evaluator) - - val flags = evaluator.evaluate(flagsArg) as? Int - - if (!isProtected) { - val actionsList = unprotectedActionsList.joinToString(", ", "", "", -1, "") - val message = "$receiverArg is missing 'RECEIVED_EXPORTED` or 'RECEIVE_NOT_EXPORTED' " + - "flag for unprotected broadcast(s) registered for $actionsList." - if (flagsArg == null) { - context.report( - ISSUE_RECEIVER_EXPORTED_FLAG, node, context.getLocation(node), message) - } else if (flags != null && (flags and RECEIVER_EXPORTED_FLAG_PRESENT_MASK) == 0) { - context.report( - ISSUE_RECEIVER_EXPORTED_FLAG, node, context.getLocation(flagsArg), message) - } - } - - if (DEBUG) { - println(node.asRenderString()) - println("Unprotected Actions: $unprotectedActionsList") - println("Protected: $isProtected") - println("Flags: $flags") - } - } - - /** Finds the first argument of a method that matches the given parameter type and name. */ - private fun findArgument( - node: UCallExpression, - method: PsiMethod, - type: String, - name: String - ): UExpression? { - val psiParameter = method.parameterList.parameters.firstOrNull { - it.type.canonicalText == type && it.name == name - } ?: return null - val argument = node.getArgumentForParameter(psiParameter.parameterIndex()) - return argument?.skipParenthesizedExprDown() - } - - /** - * For the supplied expression (eg. intent filter argument), attempts to find its construction. - * This will be an `IntentFilter()` constructor, an `IntentFilter.create()` call, or `null`. - */ - private fun findIntentFilterConstruction( - expression: UExpression, - node: UCallExpression - ): UCallExpression? { - val resolved = expression.tryResolve() - - if (resolved is PsiVariable) { - val assignment = findLastAssignment(resolved, node) ?: return null - return findIntentFilterConstruction(assignment, node) - } - - if (expression is UParenthesizedExpression) { - return findIntentFilterConstruction(expression.expression, node) - } - - if (expression is UQualifiedReferenceExpression) { - val call = expression.selector as? UCallExpression ?: return null - return if (isReturningContext(call)) { - // eg. filter.apply { addAction("abc") } --> use filter variable. - findIntentFilterConstruction(expression.receiver, node) - } else { - // eg. IntentFilter.create("abc") --> use create("abc") UCallExpression. - findIntentFilterConstruction(call, node) - } - } - - val method = resolved as? PsiMethod ?: return null - return if (isIntentFilterFactoryMethod(method)) { - expression as? UCallExpression - } else { - null - } - } - - private fun isIntentFilterFactoryMethod(method: PsiMethod) = - (method.containingClass?.qualifiedName == "android.content.IntentFilter" && - (method.returnType?.canonicalText == "android.content.IntentFilter" || - method.isConstructor)) - - /** - * Returns true if the given call represents a Kotlin scope function where the return value is - * the context object; see https://kotlinlang.org/docs/scope-functions.html#function-selection. - */ - private fun isReturningContext(node: UCallExpression): Boolean { - val name = getMethodName(node) - if (name == "apply" || name == "also") { - return isScopingFunction(node) - } - return false - } - - /** - * Returns true if the given node appears to be one of the scope functions. Only checks parent - * class; caller should intend that it's actually one of let, with, apply, etc. - */ - private fun isScopingFunction(node: UCallExpression): Boolean { - val called = node.resolve() ?: return true - // See libraries/stdlib/jvm/build/stdlib-declarations.json - return called.containingClass?.qualifiedName == "kotlin.StandardKt__StandardKt" - } - - inner class ActionCollectorVisitor( - start: Collection<UElement>, - val functionCall: UCallExpression, - val evaluator: ConstantEvaluator, - ) : DataFlowAnalyzer(start) { - private var finished = false - var intentFilterEscapesScope = false; private set - val actions = mutableSetOf<String>() - - override fun argument(call: UCallExpression, reference: UElement) { - // TODO: Remove this temporary fix for DataFlowAnalyzer bug (ag/15787550): - if (reference !in call.valueArguments) return - val methodNames = super@RegisterReceiverFlagDetector.getApplicableMethodNames() - when { - finished -> return - // We've reached the registerReceiver*() call in question. - call == functionCall -> finished = true - // The filter 'intentFilterEscapesScope' to a method which could modify it. - methodNames != null && getMethodName(call)!! !in methodNames -> - intentFilterEscapesScope = true - } - } - - // Fixed in b/199163915: DataFlowAnalyzer doesn't call this for Kotlin properties. - override fun field(field: UElement) { - if (!finished) intentFilterEscapesScope = true - } - - override fun receiver(call: UCallExpression) { - if (!finished && getMethodName(call) == "addAction") { - val actionArg = call.getArgumentForParameter(0) - if (actionArg != null) { - val action = evaluator.evaluate(actionArg) as? String - if (action != null) actions.add(action) - } - } - } - } - - companion object { - const val DEBUG = false - - private const val RECEIVER_EXPORTED = 0x2 - private const val RECEIVER_NOT_EXPORTED = 0x4 - private const val RECEIVER_EXPORTED_FLAG_PRESENT_MASK = - RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED - - @JvmField - val ISSUE_RECEIVER_EXPORTED_FLAG: Issue = Issue.create( - id = "UnspecifiedRegisterReceiverFlag", - briefDescription = "Missing `registerReceiver()` exported flag", - explanation = """ - Apps targeting Android T (SDK 33) and higher must specify either `RECEIVER_EXPORTED` \ - or `RECEIVER_NOT_EXPORTED` when registering a broadcast receiver, unless the \ - receiver is only registered for protected system broadcast actions. - """, - category = Category.SECURITY, - priority = 5, - severity = Severity.WARNING, - implementation = Implementation( - RegisterReceiverFlagDetector::class.java, - Scope.JAVA_FILE_SCOPE - ) - ) - - val PROTECTED_BROADCASTS = listOf( - "android.intent.action.SCREEN_OFF", - "android.intent.action.SCREEN_ON", - "android.intent.action.USER_PRESENT", - "android.intent.action.TIME_SET", - "android.intent.action.TIME_TICK", - "android.intent.action.TIMEZONE_CHANGED", - "android.intent.action.DATE_CHANGED", - "android.intent.action.PRE_BOOT_COMPLETED", - "android.intent.action.BOOT_COMPLETED", - "android.intent.action.PACKAGE_INSTALL", - "android.intent.action.PACKAGE_ADDED", - "android.intent.action.PACKAGE_REPLACED", - "android.intent.action.MY_PACKAGE_REPLACED", - "android.intent.action.PACKAGE_REMOVED", - "android.intent.action.PACKAGE_REMOVED_INTERNAL", - "android.intent.action.PACKAGE_FULLY_REMOVED", - "android.intent.action.PACKAGE_CHANGED", - "android.intent.action.PACKAGE_FULLY_LOADED", - "android.intent.action.PACKAGE_ENABLE_ROLLBACK", - "android.intent.action.CANCEL_ENABLE_ROLLBACK", - "android.intent.action.ROLLBACK_COMMITTED", - "android.intent.action.PACKAGE_RESTARTED", - "android.intent.action.PACKAGE_DATA_CLEARED", - "android.intent.action.PACKAGE_FIRST_LAUNCH", - "android.intent.action.PACKAGE_NEEDS_INTEGRITY_VERIFICATION", - "android.intent.action.PACKAGE_NEEDS_VERIFICATION", - "android.intent.action.PACKAGE_VERIFIED", - "android.intent.action.PACKAGES_SUSPENDED", - "android.intent.action.PACKAGES_UNSUSPENDED", - "android.intent.action.PACKAGES_SUSPENSION_CHANGED", - "android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY", - "android.intent.action.DISTRACTING_PACKAGES_CHANGED", - "android.intent.action.ACTION_PREFERRED_ACTIVITY_CHANGED", - "android.intent.action.UID_REMOVED", - "android.intent.action.QUERY_PACKAGE_RESTART", - "android.intent.action.CONFIGURATION_CHANGED", - "android.intent.action.SPLIT_CONFIGURATION_CHANGED", - "android.intent.action.LOCALE_CHANGED", - "android.intent.action.APPLICATION_LOCALE_CHANGED", - "android.intent.action.BATTERY_CHANGED", - "android.intent.action.BATTERY_LEVEL_CHANGED", - "android.intent.action.BATTERY_LOW", - "android.intent.action.BATTERY_OKAY", - "android.intent.action.ACTION_POWER_CONNECTED", - "android.intent.action.ACTION_POWER_DISCONNECTED", - "android.intent.action.ACTION_SHUTDOWN", - "android.intent.action.CHARGING", - "android.intent.action.DISCHARGING", - "android.intent.action.DEVICE_STORAGE_LOW", - "android.intent.action.DEVICE_STORAGE_OK", - "android.intent.action.DEVICE_STORAGE_FULL", - "android.intent.action.DEVICE_STORAGE_NOT_FULL", - "android.intent.action.NEW_OUTGOING_CALL", - "android.intent.action.REBOOT", - "android.intent.action.DOCK_EVENT", - "android.intent.action.THERMAL_EVENT", - "android.intent.action.MASTER_CLEAR_NOTIFICATION", - "android.intent.action.USER_ADDED", - "android.intent.action.USER_REMOVED", - "android.intent.action.USER_STARTING", - "android.intent.action.USER_STARTED", - "android.intent.action.USER_STOPPING", - "android.intent.action.USER_STOPPED", - "android.intent.action.USER_BACKGROUND", - "android.intent.action.USER_FOREGROUND", - "android.intent.action.USER_SWITCHED", - "android.intent.action.USER_INITIALIZE", - "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION", - "android.intent.action.DOMAINS_NEED_VERIFICATION", - "android.intent.action.OVERLAY_ADDED", - "android.intent.action.OVERLAY_CHANGED", - "android.intent.action.OVERLAY_REMOVED", - "android.intent.action.OVERLAY_PRIORITY_CHANGED", - "android.intent.action.MY_PACKAGE_SUSPENDED", - "android.intent.action.MY_PACKAGE_UNSUSPENDED", - "android.os.action.POWER_SAVE_MODE_CHANGED", - "android.os.action.DEVICE_IDLE_MODE_CHANGED", - "android.os.action.POWER_SAVE_WHITELIST_CHANGED", - "android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED", - "android.os.action.POWER_SAVE_MODE_CHANGED_INTERNAL", - "android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED", - "android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED", - "android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED", - "android.app.action.CLOSE_NOTIFICATION_HANDLER_PANEL", - "android.app.action.ENTER_CAR_MODE", - "android.app.action.EXIT_CAR_MODE", - "android.app.action.ENTER_CAR_MODE_PRIORITIZED", - "android.app.action.EXIT_CAR_MODE_PRIORITIZED", - "android.app.action.ENTER_DESK_MODE", - "android.app.action.EXIT_DESK_MODE", - "android.app.action.NEXT_ALARM_CLOCK_CHANGED", - "android.app.action.USER_ADDED", - "android.app.action.USER_REMOVED", - "android.app.action.USER_STARTED", - "android.app.action.USER_STOPPED", - "android.app.action.USER_SWITCHED", - "android.app.action.BUGREPORT_SHARING_DECLINED", - "android.app.action.BUGREPORT_FAILED", - "android.app.action.BUGREPORT_SHARE", - "android.app.action.SHOW_DEVICE_MONITORING_DIALOG", - "android.intent.action.PENDING_INCIDENT_REPORTS_CHANGED", - "android.intent.action.INCIDENT_REPORT_READY", - "android.appwidget.action.APPWIDGET_UPDATE_OPTIONS", - "android.appwidget.action.APPWIDGET_DELETED", - "android.appwidget.action.APPWIDGET_DISABLED", - "android.appwidget.action.APPWIDGET_ENABLED", - "android.appwidget.action.APPWIDGET_HOST_RESTORED", - "android.appwidget.action.APPWIDGET_RESTORED", - "android.appwidget.action.APPWIDGET_ENABLE_AND_UPDATE", - "android.os.action.SETTING_RESTORED", - "android.app.backup.intent.CLEAR", - "android.app.backup.intent.INIT", - "android.bluetooth.intent.DISCOVERABLE_TIMEOUT", - "android.bluetooth.adapter.action.STATE_CHANGED", - "android.bluetooth.adapter.action.SCAN_MODE_CHANGED", - "android.bluetooth.adapter.action.DISCOVERY_STARTED", - "android.bluetooth.adapter.action.DISCOVERY_FINISHED", - "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED", - "android.bluetooth.adapter.action.BLUETOOTH_ADDRESS_CHANGED", - "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.device.action.UUID", - "android.bluetooth.device.action.MAS_INSTANCE", - "android.bluetooth.device.action.ALIAS_CHANGED", - "android.bluetooth.device.action.FOUND", - "android.bluetooth.device.action.CLASS_CHANGED", - "android.bluetooth.device.action.ACL_CONNECTED", - "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED", - "android.bluetooth.device.action.ACL_DISCONNECTED", - "android.bluetooth.device.action.NAME_CHANGED", - "android.bluetooth.device.action.BOND_STATE_CHANGED", - "android.bluetooth.device.action.NAME_FAILED", - "android.bluetooth.device.action.PAIRING_REQUEST", - "android.bluetooth.device.action.PAIRING_CANCEL", - "android.bluetooth.device.action.CONNECTION_ACCESS_REPLY", - "android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL", - "android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST", - "android.bluetooth.device.action.SDP_RECORD", - "android.bluetooth.device.action.BATTERY_LEVEL_CHANGED", - "android.bluetooth.devicepicker.action.LAUNCH", - "android.bluetooth.devicepicker.action.DEVICE_SELECTED", - "android.bluetooth.action.CSIS_CONNECTION_STATE_CHANGED", - "android.bluetooth.action.CSIS_DEVICE_AVAILABLE", - "android.bluetooth.action.CSIS_SET_MEMBER_AVAILABLE", - "android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED", - "android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY", - "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY", - "android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED", - "android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED", - "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED", - "android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED", - "android.bluetooth.action.LE_AUDIO_CONF_CHANGED", - "android.bluetooth.action.LE_AUDIO_GROUP_NODE_STATUS_CHANGED", - "android.bluetooth.action.LE_AUDIO_GROUP_STATUS_CHANGED", - "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED", - "android.btopp.intent.action.INCOMING_FILE_NOTIFICATION", - "android.btopp.intent.action.USER_CONFIRMATION_TIMEOUT", - "android.btopp.intent.action.LIST", - "android.btopp.intent.action.OPEN_OUTBOUND", - "android.btopp.intent.action.HIDE_COMPLETE", - "android.btopp.intent.action.CONFIRM", - "android.btopp.intent.action.HIDE", - "android.btopp.intent.action.RETRY", - "android.btopp.intent.action.OPEN", - "android.btopp.intent.action.OPEN_INBOUND", - "android.btopp.intent.action.TRANSFER_COMPLETE", - "android.btopp.intent.action.ACCEPT", - "android.btopp.intent.action.DECLINE", - "com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN", - "com.android.bluetooth.pbap.authchall", - "com.android.bluetooth.pbap.userconfirmtimeout", - "com.android.bluetooth.pbap.authresponse", - "com.android.bluetooth.pbap.authcancelled", - "com.android.bluetooth.sap.USER_CONFIRM_TIMEOUT", - "com.android.bluetooth.sap.action.DISCONNECT_ACTION", - "android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED", - "android.hardware.usb.action.USB_STATE", - "android.hardware.usb.action.USB_PORT_CHANGED", - "android.hardware.usb.action.USB_ACCESSORY_ATTACHED", - "android.hardware.usb.action.USB_ACCESSORY_DETACHED", - "android.hardware.usb.action.USB_ACCESSORY_HANDSHAKE", - "android.hardware.usb.action.USB_DEVICE_ATTACHED", - "android.hardware.usb.action.USB_DEVICE_DETACHED", - "android.intent.action.HEADSET_PLUG", - "android.media.action.HDMI_AUDIO_PLUG", - "android.media.action.MICROPHONE_MUTE_CHANGED", - "android.media.action.SPEAKERPHONE_STATE_CHANGED", - "android.media.AUDIO_BECOMING_NOISY", - "android.media.RINGER_MODE_CHANGED", - "android.media.VIBRATE_SETTING_CHANGED", - "android.media.VOLUME_CHANGED_ACTION", - "android.media.MASTER_VOLUME_CHANGED_ACTION", - "android.media.MASTER_MUTE_CHANGED_ACTION", - "android.media.MASTER_MONO_CHANGED_ACTION", - "android.media.MASTER_BALANCE_CHANGED_ACTION", - "android.media.SCO_AUDIO_STATE_CHANGED", - "android.media.ACTION_SCO_AUDIO_STATE_UPDATED", - "android.intent.action.MEDIA_REMOVED", - "android.intent.action.MEDIA_UNMOUNTED", - "android.intent.action.MEDIA_CHECKING", - "android.intent.action.MEDIA_NOFS", - "android.intent.action.MEDIA_MOUNTED", - "android.intent.action.MEDIA_SHARED", - "android.intent.action.MEDIA_UNSHARED", - "android.intent.action.MEDIA_BAD_REMOVAL", - "android.intent.action.MEDIA_UNMOUNTABLE", - "android.intent.action.MEDIA_EJECT", - "android.net.conn.CAPTIVE_PORTAL", - "android.net.conn.CONNECTIVITY_CHANGE", - "android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE", - "android.net.conn.DATA_ACTIVITY_CHANGE", - "android.net.conn.RESTRICT_BACKGROUND_CHANGED", - "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED", - "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED", - "android.net.nsd.STATE_CHANGED", - "android.se.omapi.action.SECURE_ELEMENT_STATE_CHANGED", - "android.nfc.action.ADAPTER_STATE_CHANGED", - "android.nfc.action.PREFERRED_PAYMENT_CHANGED", - "android.nfc.action.TRANSACTION_DETECTED", - "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC", - "com.android.nfc.action.LLCP_UP", - "com.android.nfc.action.LLCP_DOWN", - "com.android.nfc.cardemulation.action.CLOSE_TAP_DIALOG", - "com.android.nfc.handover.action.ALLOW_CONNECT", - "com.android.nfc.handover.action.DENY_CONNECT", - "com.android.nfc.handover.action.TIMEOUT_CONNECT", - "com.android.nfc_extras.action.RF_FIELD_ON_DETECTED", - "com.android.nfc_extras.action.RF_FIELD_OFF_DETECTED", - "com.android.nfc_extras.action.AID_SELECTED", - "android.btopp.intent.action.WHITELIST_DEVICE", - "android.btopp.intent.action.STOP_HANDOVER_TRANSFER", - "android.nfc.handover.intent.action.HANDOVER_SEND", - "android.nfc.handover.intent.action.HANDOVER_SEND_MULTIPLE", - "com.android.nfc.handover.action.CANCEL_HANDOVER_TRANSFER", - "android.net.action.CLEAR_DNS_CACHE", - "android.intent.action.PROXY_CHANGE", - "android.os.UpdateLock.UPDATE_LOCK_CHANGED", - "android.intent.action.DREAMING_STARTED", - "android.intent.action.DREAMING_STOPPED", - "android.intent.action.ANY_DATA_STATE", - "com.android.server.stats.action.TRIGGER_COLLECTION", - "com.android.server.WifiManager.action.START_SCAN", - "com.android.server.WifiManager.action.START_PNO", - "com.android.server.WifiManager.action.DELAYED_DRIVER_STOP", - "com.android.server.WifiManager.action.DEVICE_IDLE", - "com.android.server.action.REMOTE_BUGREPORT_SHARING_ACCEPTED", - "com.android.server.action.REMOTE_BUGREPORT_SHARING_DECLINED", - "com.android.internal.action.EUICC_FACTORY_RESET", - "com.android.server.usb.ACTION_OPEN_IN_APPS", - "com.android.server.am.DELETE_DUMPHEAP", - "com.android.server.net.action.SNOOZE_WARNING", - "com.android.server.net.action.SNOOZE_RAPID", - "com.android.server.wifi.ACTION_SHOW_SET_RANDOMIZATION_DETAILS", - "com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_APP", - "com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_APP", - "com.android.server.wifi.action.NetworkSuggestion.USER_DISMISSED", - "com.android.server.wifi.action.CarrierNetwork.USER_ALLOWED_CARRIER", - "com.android.server.wifi.action.CarrierNetwork.USER_DISALLOWED_CARRIER", - "com.android.server.wifi.action.CarrierNetwork.USER_DISMISSED", - "com.android.server.wifi.ConnectToNetworkNotification.USER_DISMISSED_NOTIFICATION", - "com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK", - "com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK", - "com.android.server.wifi.ConnectToNetworkNotification.PICK_NETWORK_AFTER_FAILURE", - "com.android.server.wifi.wakeup.DISMISS_NOTIFICATION", - "com.android.server.wifi.wakeup.OPEN_WIFI_PREFERENCES", - "com.android.server.wifi.wakeup.OPEN_WIFI_SETTINGS", - "com.android.server.wifi.wakeup.TURN_OFF_WIFI_WAKE", - "android.net.wifi.WIFI_STATE_CHANGED", - "android.net.wifi.WIFI_AP_STATE_CHANGED", - "android.net.wifi.WIFI_CREDENTIAL_CHANGED", - "android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED", - "android.net.wifi.aware.action.WIFI_AWARE_RESOURCE_CHANGED", - "android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED", - "android.net.wifi.SCAN_RESULTS", - "android.net.wifi.RSSI_CHANGED", - "android.net.wifi.STATE_CHANGE", - "android.net.wifi.LINK_CONFIGURATION_CHANGED", - "android.net.wifi.CONFIGURED_NETWORKS_CHANGE", - "android.net.wifi.action.NETWORK_SETTINGS_RESET", - "android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT", - "android.net.wifi.action.PASSPOINT_ICON", - "android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST", - "android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION", - "android.net.wifi.action.PASSPOINT_LAUNCH_OSU_VIEW", - "android.net.wifi.action.REFRESH_USER_PROVISIONING", - "android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION", - "android.net.wifi.action.WIFI_SCAN_AVAILABILITY_CHANGED", - "android.net.wifi.supplicant.CONNECTION_CHANGE", - "android.net.wifi.supplicant.STATE_CHANGE", - "android.net.wifi.p2p.STATE_CHANGED", - "android.net.wifi.p2p.DISCOVERY_STATE_CHANGE", - "android.net.wifi.p2p.THIS_DEVICE_CHANGED", - "android.net.wifi.p2p.PEERS_CHANGED", - "android.net.wifi.p2p.CONNECTION_STATE_CHANGE", - "android.net.wifi.p2p.action.WIFI_P2P_PERSISTENT_GROUPS_CHANGED", - "android.net.conn.TETHER_STATE_CHANGED", - "android.net.conn.INET_CONDITION_ACTION", - "android.net.conn.NETWORK_CONDITIONS_MEASURED", - "android.net.scoring.SCORE_NETWORKS", - "android.net.scoring.SCORER_CHANGED", - "android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE", - "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE", - "android.intent.action.AIRPLANE_MODE", - "android.intent.action.ADVANCED_SETTINGS", - "android.intent.action.APPLICATION_RESTRICTIONS_CHANGED", - "com.android.server.adb.WIRELESS_DEBUG_PAIRED_DEVICES", - "com.android.server.adb.WIRELESS_DEBUG_PAIRING_RESULT", - "com.android.server.adb.WIRELESS_DEBUG_STATUS", - "android.intent.action.ACTION_IDLE_MAINTENANCE_START", - "android.intent.action.ACTION_IDLE_MAINTENANCE_END", - "com.android.server.ACTION_TRIGGER_IDLE", - "android.intent.action.HDMI_PLUGGED", - "android.intent.action.PHONE_STATE", - "android.intent.action.SUB_DEFAULT_CHANGED", - "android.location.PROVIDERS_CHANGED", - "android.location.MODE_CHANGED", - "android.location.action.GNSS_CAPABILITIES_CHANGED", - "android.net.proxy.PAC_REFRESH", - "android.telecom.action.DEFAULT_DIALER_CHANGED", - "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED", - "android.provider.action.SMS_MMS_DB_CREATED", - "android.provider.action.SMS_MMS_DB_LOST", - "android.intent.action.CONTENT_CHANGED", - "android.provider.Telephony.MMS_DOWNLOADED", - "android.content.action.PERMISSION_RESPONSE_RECEIVED", - "android.content.action.REQUEST_PERMISSION", - "android.nfc.handover.intent.action.HANDOVER_STARTED", - "android.nfc.handover.intent.action.TRANSFER_DONE", - "android.nfc.handover.intent.action.TRANSFER_PROGRESS", - "android.nfc.handover.intent.action.TRANSFER_DONE", - "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED", - "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED", - "android.intent.action.ACTION_SET_RADIO_CAPABILITY_DONE", - "android.intent.action.ACTION_SET_RADIO_CAPABILITY_FAILED", - "android.internal.policy.action.BURN_IN_PROTECTION", - "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED", - "android.app.action.RESET_PROTECTION_POLICY_CHANGED", - "android.app.action.DEVICE_OWNER_CHANGED", - "android.app.action.MANAGED_USER_CREATED", - "android.intent.action.ANR", - "android.intent.action.CALL", - "android.intent.action.CALL_PRIVILEGED", - "android.intent.action.DROPBOX_ENTRY_ADDED", - "android.intent.action.INPUT_METHOD_CHANGED", - "android.intent.action.internal_sim_state_changed", - "android.intent.action.LOCKED_BOOT_COMPLETED", - "android.intent.action.PRECISE_CALL_STATE", - "android.intent.action.SUBSCRIPTION_PHONE_STATE", - "android.intent.action.USER_INFO_CHANGED", - "android.intent.action.USER_UNLOCKED", - "android.intent.action.WALLPAPER_CHANGED", - "android.app.action.DEVICE_POLICY_MANAGER_STATE_CHANGED", - "android.app.action.CHOOSE_PRIVATE_KEY_ALIAS", - "android.app.action.DEVICE_ADMIN_DISABLED", - "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED", - "android.app.action.DEVICE_ADMIN_ENABLED", - "android.app.action.LOCK_TASK_ENTERING", - "android.app.action.LOCK_TASK_EXITING", - "android.app.action.NOTIFY_PENDING_SYSTEM_UPDATE", - "android.app.action.ACTION_PASSWORD_CHANGED", - "android.app.action.ACTION_PASSWORD_EXPIRING", - "android.app.action.ACTION_PASSWORD_FAILED", - "android.app.action.ACTION_PASSWORD_SUCCEEDED", - "com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION", - "com.android.server.ACTION_PROFILE_OFF_DEADLINE", - "com.android.server.ACTION_TURN_PROFILE_ON_NOTIFICATION", - "android.intent.action.MANAGED_PROFILE_ADDED", - "android.intent.action.MANAGED_PROFILE_UNLOCKED", - "android.intent.action.MANAGED_PROFILE_REMOVED", - "android.app.action.MANAGED_PROFILE_PROVISIONED", - "android.bluetooth.adapter.action.BLE_STATE_CHANGED", - "com.android.bluetooth.map.USER_CONFIRM_TIMEOUT", - "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT", - "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY", - "android.content.jobscheduler.JOB_DELAY_EXPIRED", - "android.content.syncmanager.SYNC_ALARM", - "android.media.INTERNAL_RINGER_MODE_CHANGED_ACTION", - "android.media.STREAM_DEVICES_CHANGED_ACTION", - "android.media.STREAM_MUTE_CHANGED_ACTION", - "android.net.sip.SIP_SERVICE_UP", - "android.nfc.action.ADAPTER_STATE_CHANGED", - "android.os.action.CHARGING", - "android.os.action.DISCHARGING", - "android.search.action.SEARCHABLES_CHANGED", - "android.security.STORAGE_CHANGED", - "android.security.action.TRUST_STORE_CHANGED", - "android.security.action.KEYCHAIN_CHANGED", - "android.security.action.KEY_ACCESS_CHANGED", - "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED", - "android.telecom.action.PHONE_ACCOUNT_REGISTERED", - "android.telecom.action.PHONE_ACCOUNT_UNREGISTERED", - "android.telecom.action.POST_CALL", - "android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION", - "android.telephony.action.CARRIER_CONFIG_CHANGED", - "android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED", - "android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED", - "android.telephony.action.SECRET_CODE", - "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION", - "android.telephony.action.SUBSCRIPTION_PLANS_CHANGED", - "com.android.bluetooth.btservice.action.ALARM_WAKEUP", - "com.android.server.action.NETWORK_STATS_POLL", - "com.android.server.action.NETWORK_STATS_UPDATED", - "com.android.server.timedetector.NetworkTimeUpdateService.action.POLL", - "com.android.server.telecom.intent.action.CALLS_ADD_ENTRY", - "com.android.settings.location.MODE_CHANGING", - "com.android.settings.bluetooth.ACTION_DISMISS_PAIRING", - "com.android.settings.network.DELETE_SUBSCRIPTION", - "com.android.settings.network.SWITCH_TO_SUBSCRIPTION", - "com.android.settings.wifi.action.NETWORK_REQUEST", - "NotificationManagerService.TIMEOUT", - "NotificationHistoryDatabase.CLEANUP", - "ScheduleConditionProvider.EVALUATE", - "EventConditionProvider.EVALUATE", - "SnoozeHelper.EVALUATE", - "wifi_scan_available", - "action.cne.started", - "android.content.jobscheduler.JOB_DEADLINE_EXPIRED", - "android.intent.action.ACTION_UNSOL_RESPONSE_OEM_HOOK_RAW", - "android.net.conn.CONNECTIVITY_CHANGE_SUPL", - "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED", - "android.os.storage.action.VOLUME_STATE_CHANGED", - "android.os.storage.action.DISK_SCANNED", - "com.android.server.action.UPDATE_TWILIGHT_STATE", - "com.android.server.action.RESET_TWILIGHT_AUTO", - "com.android.server.device_idle.STEP_IDLE_STATE", - "com.android.server.device_idle.STEP_LIGHT_IDLE_STATE", - "com.android.server.Wifi.action.TOGGLE_PNO", - "intent.action.ACTION_RF_BAND_INFO", - "android.intent.action.MEDIA_RESOURCE_GRANTED", - "android.app.action.NETWORK_LOGS_AVAILABLE", - "android.app.action.SECURITY_LOGS_AVAILABLE", - "android.app.action.COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED", - "android.app.action.INTERRUPTION_FILTER_CHANGED", - "android.app.action.INTERRUPTION_FILTER_CHANGED_INTERNAL", - "android.app.action.NOTIFICATION_POLICY_CHANGED", - "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED", - "android.app.action.AUTOMATIC_ZEN_RULE_STATUS_CHANGED", - "android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED", - "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED", - "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED", - "android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED", - "android.app.action.APP_BLOCK_STATE_CHANGED", - "android.permission.GET_APP_GRANTED_URI_PERMISSIONS", - "android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS", - "android.intent.action.DYNAMIC_SENSOR_CHANGED", - "android.accounts.LOGIN_ACCOUNTS_CHANGED", - "android.accounts.action.ACCOUNT_REMOVED", - "android.accounts.action.VISIBLE_ACCOUNTS_CHANGED", - "com.android.sync.SYNC_CONN_STATUS_CHANGED", - "android.net.sip.action.SIP_INCOMING_CALL", - "com.android.phone.SIP_ADD_PHONE", - "android.net.sip.action.SIP_REMOVE_PROFILE", - "android.net.sip.action.SIP_SERVICE_UP", - "android.net.sip.action.SIP_CALL_OPTION_CHANGED", - "android.net.sip.action.START_SIP", - "android.bluetooth.adapter.action.BLE_ACL_CONNECTED", - "android.bluetooth.adapter.action.BLE_ACL_DISCONNECTED", - "android.bluetooth.input.profile.action.HANDSHAKE", - "android.bluetooth.input.profile.action.REPORT", - "android.intent.action.TWILIGHT_CHANGED", - "com.android.server.fingerprint.ACTION_LOCKOUT_RESET", - "android.net.wifi.PASSPOINT_ICON_RECEIVED", - "com.android.server.notification.CountdownConditionProvider", - "android.server.notification.action.ENABLE_NAS", - "android.server.notification.action.DISABLE_NAS", - "android.server.notification.action.LEARNMORE_NAS", - "com.android.internal.location.ALARM_WAKEUP", - "com.android.internal.location.ALARM_TIMEOUT", - "android.intent.action.GLOBAL_BUTTON", - "android.intent.action.MANAGED_PROFILE_AVAILABLE", - "android.intent.action.MANAGED_PROFILE_UNAVAILABLE", - "com.android.server.pm.DISABLE_QUIET_MODE_AFTER_UNLOCK", - "android.intent.action.PROFILE_ACCESSIBLE", - "android.intent.action.PROFILE_INACCESSIBLE", - "com.android.server.retaildemo.ACTION_RESET_DEMO", - "android.intent.action.DEVICE_LOCKED_CHANGED", - "com.android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED", - "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED", - "com.android.server.wm.ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION", - "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED", - "android.content.pm.action.SESSION_COMMITTED", - "android.os.action.USER_RESTRICTIONS_CHANGED", - "android.media.tv.action.PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT", - "android.media.tv.action.PREVIEW_PROGRAM_BROWSABLE_DISABLED", - "android.media.tv.action.WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED", - "android.media.tv.action.CHANNEL_BROWSABLE_REQUESTED", - "com.android.server.inputmethod.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER", - "com.android.intent.action.timezone.RULES_UPDATE_OPERATION", - "com.android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK", - "android.intent.action.GET_RESTRICTION_ENTRIES", - "android.telephony.euicc.action.OTA_STATUS_CHANGED", - "android.app.action.PROFILE_OWNER_CHANGED", - "android.app.action.TRANSFER_OWNERSHIP_COMPLETE", - "android.app.action.AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE", - "android.app.action.STATSD_STARTED", - "com.android.server.biometrics.fingerprint.ACTION_LOCKOUT_RESET", - "com.android.server.biometrics.face.ACTION_LOCKOUT_RESET", - "android.intent.action.DOCK_IDLE", - "android.intent.action.DOCK_ACTIVE", - "android.content.pm.action.SESSION_UPDATED", - "android.settings.action.GRAYSCALE_CHANGED", - "com.android.server.jobscheduler.GARAGE_MODE_ON", - "com.android.server.jobscheduler.GARAGE_MODE_OFF", - "com.android.server.jobscheduler.FORCE_IDLE", - "com.android.server.jobscheduler.UNFORCE_IDLE", - "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL", - "android.intent.action.DEVICE_CUSTOMIZATION_READY", - "android.app.action.RESET_PROTECTION_POLICY_CHANGED", - "com.android.internal.intent.action.BUGREPORT_REQUESTED", - "android.scheduling.action.REBOOT_READY", - "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED", - "android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED", - "android.app.action.SHOW_NEW_USER_DISCLAIMER", - "android.telecom.action.CURRENT_TTY_MODE_CHANGED", - "android.intent.action.SERVICE_STATE", - "android.intent.action.RADIO_TECHNOLOGY", - "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED", - "android.intent.action.EMERGENCY_CALL_STATE_CHANGED", - "android.intent.action.SIG_STR", - "android.intent.action.ANY_DATA_STATE", - "android.intent.action.DATA_STALL_DETECTED", - "android.intent.action.SIM_STATE_CHANGED", - "android.intent.action.USER_ACTIVITY_NOTIFICATION", - "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS", - "android.intent.action.ACTION_MDN_STATE_CHANGED", - "android.telephony.action.SERVICE_PROVIDERS_UPDATED", - "android.provider.Telephony.SIM_FULL", - "com.android.internal.telephony.carrier_key_download_alarm", - "com.android.internal.telephony.data-restart-trysetup", - "com.android.internal.telephony.data-stall", - "com.android.internal.telephony.provisioning_apn_alarm", - "android.intent.action.DATA_SMS_RECEIVED", - "android.provider.Telephony.SMS_RECEIVED", - "android.provider.Telephony.SMS_DELIVER", - "android.provider.Telephony.SMS_REJECTED", - "android.provider.Telephony.WAP_PUSH_DELIVER", - "android.provider.Telephony.WAP_PUSH_RECEIVED", - "android.provider.Telephony.SMS_CB_RECEIVED", - "android.provider.action.SMS_EMERGENCY_CB_RECEIVED", - "android.provider.Telephony.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED", - "android.provider.Telephony.SECRET_CODE", - "com.android.internal.stk.command", - "com.android.internal.stk.session_end", - "com.android.internal.stk.icc_status_change", - "com.android.internal.stk.alpha_notify", - "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED", - "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED", - "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE", - "com.android.internal.telephony.CARRIER_SIGNAL_RESET", - "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE", - "com.android.internal.telephony.PROVISION", - "com.android.internal.telephony.ACTION_LINE1_NUMBER_ERROR_DETECTED", - "com.android.internal.provider.action.VOICEMAIL_SMS_RECEIVED", - "com.android.intent.isim_refresh", - "com.android.ims.ACTION_RCS_SERVICE_AVAILABLE", - "com.android.ims.ACTION_RCS_SERVICE_UNAVAILABLE", - "com.android.ims.ACTION_RCS_SERVICE_DIED", - "com.android.ims.ACTION_PRESENCE_CHANGED", - "com.android.ims.ACTION_PUBLISH_STATUS_CHANGED", - "com.android.ims.IMS_SERVICE_UP", - "com.android.ims.IMS_SERVICE_DOWN", - "com.android.ims.IMS_INCOMING_CALL", - "com.android.ims.internal.uce.UCE_SERVICE_UP", - "com.android.ims.internal.uce.UCE_SERVICE_DOWN", - "com.android.imsconnection.DISCONNECTED", - "com.android.intent.action.IMS_FEATURE_CHANGED", - "com.android.intent.action.IMS_CONFIG_CHANGED", - "android.telephony.ims.action.WFC_IMS_REGISTRATION_ERROR", - "com.android.phone.vvm.omtp.sms.REQUEST_SENT", - "com.android.phone.vvm.ACTION_VISUAL_VOICEMAIL_SERVICE_EVENT", - "com.android.internal.telephony.CARRIER_VVM_PACKAGE_INSTALLED", - "com.android.cellbroadcastreceiver.GET_LATEST_CB_AREA_INFO", - "com.android.internal.telephony.ACTION_CARRIER_CERTIFICATE_DOWNLOAD", - "com.android.internal.telephony.action.COUNTRY_OVERRIDE", - "com.android.internal.telephony.OPEN_DEFAULT_SMS_APP", - "com.android.internal.telephony.ACTION_TEST_OVERRIDE_CARRIER_ID", - "android.telephony.action.SIM_CARD_STATE_CHANGED", - "android.telephony.action.SIM_APPLICATION_STATE_CHANGED", - "android.telephony.action.SIM_SLOT_STATUS_CHANGED", - "android.telephony.action.SUBSCRIPTION_CARRIER_IDENTITY_CHANGED", - "android.telephony.action.SUBSCRIPTION_SPECIFIC_CARRIER_IDENTITY_CHANGED", - "android.telephony.action.TOGGLE_PROVISION", - "android.telephony.action.NETWORK_COUNTRY_CHANGED", - "android.telephony.action.PRIMARY_SUBSCRIPTION_LIST_CHANGED", - "android.telephony.action.MULTI_SIM_CONFIG_CHANGED", - "android.telephony.action.CARRIER_SIGNAL_RESET", - "android.telephony.action.CARRIER_SIGNAL_PCO_VALUE", - "android.telephony.action.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE", - "android.telephony.action.CARRIER_SIGNAL_REDIRECTED", - "android.telephony.action.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED", - "com.android.phone.settings.CARRIER_PROVISIONING", - "com.android.phone.settings.TRIGGER_CARRIER_PROVISIONING", - "com.android.internal.telephony.ACTION_VOWIFI_ENABLED", - "android.telephony.action.ANOMALY_REPORTED", - "android.intent.action.SUBSCRIPTION_INFO_RECORD_ADDED", - "android.intent.action.ACTION_MANAGED_ROAMING_IND", - "android.telephony.ims.action.RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE", - "android.safetycenter.action.REFRESH_SAFETY_SOURCES", - "android.safetycenter.action.SAFETY_CENTER_ENABLED_CHANGED", - "android.app.action.DEVICE_POLICY_RESOURCE_UPDATED", - "android.intent.action.SHOW_FOREGROUND_SERVICE_MANAGER", - "android.service.autofill.action.DELAYED_FILL", - "android.app.action.PROVISIONING_COMPLETED", - "android.app.action.LOST_MODE_LOCATION_UPDATE", - "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED", - "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT", - "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED", - "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED", - "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED", - "android.bluetooth.headsetclient.profile.action.AG_EVENT", - "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED", - "android.bluetooth.headsetclient.profile.action.RESULT", - "android.bluetooth.headsetclient.profile.action.LAST_VTAG", - "android.bluetooth.headsetclient.profile.action.NETWORK_SERVICE_STATE_CHANGED", - "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED", - "android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED", - "android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED", - "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED", - "android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED", - "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.a2dp-sink.profile.action.PLAYING_STATE_CHANGED", - "android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED", - "android.bluetooth.avrcp-controller.profile.action.BROWSE_CONNECTION_STATE_CHANGED", - "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.avrcp-controller.profile.action.FOLDER_LIST", - "android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT", - "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.input.profile.action.IDLE_TIME_CHANGED", - "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED", - "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS", - "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED", - "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT", - "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY", - "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.action.TETHERING_STATE_CHANGED", - "com.android.internal.action.EUICC_REMOVE_INVISIBLE_SUBSCRIPTIONS", - "android.net.ConnectivityService.action.PKT_CNT_SAMPLE_INTERVAL_ELAPSED", - "com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION", - "com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" - ) - } -} diff --git a/tools/lint/framework/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt deleted file mode 100644 index 7c0ebca4ec1e..000000000000 --- a/tools/lint/framework/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt +++ /dev/null @@ -1,569 +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.google.android.lint - -import com.android.tools.lint.checks.infrastructure.LintDetectorTest -import com.android.tools.lint.checks.infrastructure.TestFile -import com.android.tools.lint.checks.infrastructure.TestLintTask -import com.android.tools.lint.detector.api.Detector -import com.android.tools.lint.detector.api.Issue - -@Suppress("UnstableApiUsage") -class RegisterReceiverFlagDetectorTest : LintDetectorTest() { - - override fun getDetector(): Detector = RegisterReceiverFlagDetector() - - override fun getIssues(): List<Issue> = listOf( - RegisterReceiverFlagDetector.ISSUE_RECEIVER_EXPORTED_FLAG - ) - - override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) - - fun testProtectedBroadcast() { - lint().files( - java( - """ - package test.pkg; - import android.content.BroadcastReceiver; - import android.content.Context; - import android.content.Intent; - import android.content.IntentFilter; - public class TestClass1 { - public void testMethod(Context context, BroadcastReceiver receiver) { - IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); - context.registerReceiver(receiver, filter); - } - } - """ - ).indented(), - *stubs - ) - .run() - .expectClean() - } - - fun testProtectedBroadcastCreate() { - lint().files( - java( - """ - package test.pkg; - import android.content.BroadcastReceiver; - import android.content.Context; - import android.content.Intent; - import android.content.IntentFilter; - public class TestClass1 { - public void testMethod(Context context, BroadcastReceiver receiver) { - IntentFilter filter = - IntentFilter.create(Intent.ACTION_BATTERY_CHANGED, "foo/bar"); - context.registerReceiver(receiver, filter); - } - } - """ - ).indented(), - *stubs - ) - .run() - .expectClean() - } - - fun testMultipleProtectedBroadcasts() { - lint().files( - java( - """ - package test.pkg; - import android.content.BroadcastReceiver; - import android.content.Context; - import android.content.Intent; - import android.content.IntentFilter; - public class TestClass1 { - public void testMethod(Context context, BroadcastReceiver receiver) { - IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(Intent.ACTION_BATTERY_LOW); - filter.addAction(Intent.ACTION_BATTERY_OKAY); - context.registerReceiver(receiver, filter); - } - } - """ - ).indented(), - *stubs - ) - .run() - .expectClean() - } - - // TODO(b/267510341): Reenable this test - // fun testSubsequentFilterModification() { - // lint().files( - // java( - // """ - // package test.pkg; - // import android.content.BroadcastReceiver; - // import android.content.Context; - // import android.content.Intent; - // import android.content.IntentFilter; - // public class TestClass1 { - // public void testMethod(Context context, BroadcastReceiver receiver) { - // IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); - // filter.addAction(Intent.ACTION_BATTERY_LOW); - // filter.addAction(Intent.ACTION_BATTERY_OKAY); - // context.registerReceiver(receiver, filter); - // filter.addAction("querty"); - // context.registerReceiver(receiver, filter); - // } - // } - // """ - // ).indented(), - // *stubs - // ) - // .run() - // .expect(""" - // src/test/pkg/TestClass1.java:13: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag] - // context.registerReceiver(receiver, filter); - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // 0 errors, 1 warnings - // """.trimIndent()) - // } - - fun testNullReceiver() { - lint().files( - java( - """ - package test.pkg; - import android.content.BroadcastReceiver; - import android.content.Context; - import android.content.Intent; - import android.content.IntentFilter; - public class TestClass1 { - public void testMethod(Context context) { - IntentFilter filter = new IntentFilter("qwerty"); - context.registerReceiver(null, filter); - } - } - """ - ).indented(), - *stubs - ) - .run() - .expectClean() - } - - fun testExportedFlagPresent() { - lint().files( - java( - """ - package test.pkg; - import android.content.BroadcastReceiver; - import android.content.Context; - import android.content.Intent; - import android.content.IntentFilter; - public class TestClass1 { - public void testMethod(Context context, BroadcastReceiver receiver) { - IntentFilter filter = new IntentFilter("qwerty"); - context.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED); - } - } - """ - ).indented(), - *stubs - ) - .run() - .expectClean() - } - - fun testNotExportedFlagPresent() { - lint().files( - java( - """ - package test.pkg; - import android.content.BroadcastReceiver; - import android.content.Context; - import android.content.Intent; - import android.content.IntentFilter; - public class TestClass1 { - public void testMethod(Context context, BroadcastReceiver receiver) { - IntentFilter filter = new IntentFilter("qwerty"); - context.registerReceiver(receiver, filter, - Context.RECEIVER_NOT_EXPORTED); - } - } - """ - ).indented(), - *stubs - ) - .run() - .expectClean() - } - - // TODO(b/267510341): Reenable this test - // fun testFlagArgumentAbsent() { - // lint().files( - // java( - // """ - // package test.pkg; - // import android.content.BroadcastReceiver; - // import android.content.Context; - // import android.content.Intent; - // import android.content.IntentFilter; - // public class TestClass1 { - // public void testMethod(Context context, BroadcastReceiver receiver) { - // IntentFilter filter = new IntentFilter("qwerty"); - // context.registerReceiver(receiver, filter); - // } - // } - // """ - // ).indented(), - // *stubs - // ) - // .run() - // .expect(""" - // src/test/pkg/TestClass1.java:9: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag] - // context.registerReceiver(receiver, filter); - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // 0 errors, 1 warnings - // """.trimIndent()) - // } - - // TODO(b/267510341): Reenable this test - // fun testExportedFlagsAbsent() { - // lint().files( - // java( - // """ - // package test.pkg; - // import android.content.BroadcastReceiver; - // import android.content.Context; - // import android.content.Intent; - // import android.content.IntentFilter; - // public class TestClass1 { - // public void testMethod(Context context, BroadcastReceiver receiver) { - // IntentFilter filter = new IntentFilter("qwerty"); - // context.registerReceiver(receiver, filter, 0); - // } - // } - // """ - // ).indented(), - // *stubs - // ) - // .run() - // .expect(""" - // src/test/pkg/TestClass1.java:9: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag] - // context.registerReceiver(receiver, filter, 0); - // ~ - // 0 errors, 1 warnings - // """.trimIndent()) - // } - - fun testExportedFlagVariable() { - lint().files( - java( - """ - package test.pkg; - import android.content.BroadcastReceiver; - import android.content.Context; - import android.content.Intent; - import android.content.IntentFilter; - public class TestClass1 { - public void testMethod(Context context, BroadcastReceiver receiver) { - IntentFilter filter = new IntentFilter("qwerty"); - var flags = Context.RECEIVER_EXPORTED; - context.registerReceiver(receiver, filter, flags); - } - } - """ - ).indented(), - *stubs - ) - .run() - .expectClean() - } - - // TODO(b/267510341): Reenable this test - // fun testUnknownFilter() { - // lint().files( - // java( - // """ - // package test.pkg; - // import android.content.BroadcastReceiver; - // import android.content.Context; - // import android.content.Intent; - // import android.content.IntentFilter; - // public class TestClass1 { - // public void testMethod(Context context, BroadcastReceiver receiver, - // IntentFilter filter) { - // context.registerReceiver(receiver, filter); - // } - // } - // """ - // ).indented(), - // *stubs - // ) - // .run() - // .expect(""" - // src/test/pkg/TestClass1.java:9: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag] - // context.registerReceiver(receiver, filter); - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // 0 errors, 1 warnings - // """.trimIndent()) - // } - - // TODO(b/267510341): Reenable this test - // fun testFilterEscapes() { - // lint().files( - // java( - // """ - // package test.pkg; - // import android.content.BroadcastReceiver; - // import android.content.Context; - // import android.content.Intent; - // import android.content.IntentFilter; - // public class TestClass1 { - // public void testMethod(Context context, BroadcastReceiver receiver) { - // IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); - // updateFilter(filter); - // context.registerReceiver(receiver, filter); - // } - // } - // """ - // ).indented(), - // *stubs - // ) - // .run() - // .expect(""" - // src/test/pkg/TestClass1.java:10: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag] - // context.registerReceiver(receiver, filter); - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // 0 errors, 1 warnings - // """.trimIndent()) - // } - - fun testInlineFilter() { - lint().files( - java( - """ - package test.pkg; - import android.content.BroadcastReceiver; - import android.content.Context; - import android.content.Intent; - import android.content.IntentFilter; - public class TestClass1 { - public void testMethod(Context context, BroadcastReceiver receiver) { - context.registerReceiver(receiver, - new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); - } - } - """ - ).indented(), - *stubs - ) - .run() - .expectClean() - } - - // TODO(b/267510341): Reenable this test - // fun testInlineFilterApply() { - // lint().files( - // kotlin( - // """ - // package test.pkg - // import android.content.BroadcastReceiver - // import android.content.Context - // import android.content.Intent - // import android.content.IntentFilter - // class TestClass1 { - // fun test(context: Context, receiver: BroadcastReceiver) { - // context.registerReceiver(receiver, - // IntentFilter(Intent.ACTION_BATTERY_CHANGED).apply { - // addAction("qwerty") - // }) - // } - // } - // """ - // ).indented(), - // *stubs - // ) - // .run() - // .expect(""" - // src/test/pkg/TestClass1.kt:8: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag] - // context.registerReceiver(receiver, - // ^ - // 0 errors, 1 warnings - // """.trimIndent()) - // } - - // TODO(b/267510341): Reenable this test - // fun testFilterVariableApply() { - // lint().files( - // kotlin( - // """ - // package test.pkg - // import android.content.BroadcastReceiver - // import android.content.Context - // import android.content.Intent - // import android.content.IntentFilter - // class TestClass1 { - // fun test(context: Context, receiver: BroadcastReceiver) { - // val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED).apply { - // addAction("qwerty") - // } - // context.registerReceiver(receiver, filter) - // } - // } - // """ - // ).indented(), - // *stubs - // ) - // .run() - // .expect(""" - // src/test/pkg/TestClass1.kt:11: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag] - // context.registerReceiver(receiver, filter) - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // 0 errors, 1 warnings - // """.trimIndent()) - // } - - // TODO(b/267510341): Reenable this test - // fun testFilterVariableApply2() { - // lint().files( - // kotlin( - // """ - // package test.pkg - // import android.content.BroadcastReceiver - // import android.content.Context - // import android.content.Intent - // import android.content.IntentFilter - // class TestClass1 { - // fun test(context: Context, receiver: BroadcastReceiver) { - // val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED).apply { - // addAction(Intent.ACTION_BATTERY_OKAY) - // } - // context.registerReceiver(receiver, filter.apply { - // addAction("qwerty") - // }) - // } - // } - // """ - // ).indented(), - // *stubs - // ) - // .run() - // .expect(""" - // src/test/pkg/TestClass1.kt:11: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag] - // context.registerReceiver(receiver, filter.apply { - // ^ - // 0 errors, 1 warnings - // """.trimIndent()) - // } - - // TODO(b/267510341): Reenable this test - // fun testFilterComplexChain() { - // lint().files( - // kotlin( - // """ - // package test.pkg - // import android.content.BroadcastReceiver - // import android.content.Context - // import android.content.Intent - // import android.content.IntentFilter - // class TestClass1 { - // fun test(context: Context, receiver: BroadcastReceiver) { - // val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED).apply { - // addAction(Intent.ACTION_BATTERY_OKAY) - // } - // val filter2 = filter - // val filter3 = filter2.apply { - // addAction(Intent.ACTION_BATTERY_LOW) - // } - // context.registerReceiver(receiver, filter3) - // val filter4 = filter3.apply { - // addAction("qwerty") - // } - // context.registerReceiver(receiver, filter4) - // } - // } - // """ - // ).indented(), - // *stubs - // ) - // .run() - // .expect(""" - // src/test/pkg/TestClass1.kt:19: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag] - // context.registerReceiver(receiver, filter4) - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // 0 errors, 1 warnings - // """.trimIndent()) - // } - - private val broadcastReceiverStub: TestFile = java( - """ - package android.content; - public class BroadcastReceiver { - // Stub - } - """ - ).indented() - - private val contextStub: TestFile = java( - """ - package android.content; - public class Context { - public static final int RECEIVER_EXPORTED = 0x2; - public static final int RECEIVER_NOT_EXPORTED = 0x4; - @Nullable - public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver, - IntentFilter filter, - @RegisterReceiverFlags int flags); - } - """ - ).indented() - - private val intentStub: TestFile = java( - """ - package android.content; - public class Intent { - public static final String ACTION_BATTERY_CHANGED = - "android.intent.action.BATTERY_CHANGED"; - public static final String ACTION_BATTERY_LOW = "android.intent.action.BATTERY_LOW"; - public static final String ACTION_BATTERY_OKAY = - "android.intent.action.BATTERY_OKAY"; - } - """ - ).indented() - - private val intentFilterStub: TestFile = java( - """ - package android.content; - public class IntentFilter { - public IntentFilter() { - // Stub - } - public IntentFilter(String action) { - // Stub - } - public IntentFilter(String action, String dataType) { - // Stub - } - public static IntentFilter create(String action, String dataType) { - return null; - } - public final void addAction(String action) { - // Stub - } - } - """ - ).indented() - - private val stubs = arrayOf(broadcastReceiverStub, contextStub, intentStub, intentFilterStub) -} diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java index 4809befb8d20..63e471b25ffb 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java @@ -17,7 +17,11 @@ package android.net.wifi.sharedconnectivity.app; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -37,6 +41,7 @@ import java.util.Objects; public final class SharedConnectivitySettingsState implements Parcelable { private final boolean mInstantTetherEnabled; + private final PendingIntent mInstantTetherSettingsPendingIntent; private final Bundle mExtras; /** @@ -44,9 +49,13 @@ public final class SharedConnectivitySettingsState implements Parcelable { */ public static final class Builder { private boolean mInstantTetherEnabled; + private Intent mInstantTetherSettingsIntent; + private final Context mContext; private Bundle mExtras; - public Builder() {} + public Builder(@NonNull Context context) { + mContext = context; + } /** * Sets the state of Instant Tether in settings @@ -60,6 +69,20 @@ public final class SharedConnectivitySettingsState implements Parcelable { } /** + * Sets the intent that will open the Instant Tether settings page. + * The intent will be stored as a {@link PendingIntent} in the settings object. The pending + * intent will be set as {@link PendingIntent#FLAG_IMMUTABLE} and + * {@link PendingIntent#FLAG_ONE_SHOT}. + * + * @return Returns the Builder object. + */ + @NonNull + public Builder setInstantTetherSettingsPendingIntent(@NonNull Intent intent) { + mInstantTetherSettingsIntent = intent; + return this; + } + + /** * Sets the extras bundle * * @return Returns the Builder object. @@ -77,12 +100,22 @@ public final class SharedConnectivitySettingsState implements Parcelable { */ @NonNull public SharedConnectivitySettingsState build() { - return new SharedConnectivitySettingsState(mInstantTetherEnabled, mExtras); + if (mInstantTetherSettingsIntent != null) { + PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, + mInstantTetherSettingsIntent, + PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT); + return new SharedConnectivitySettingsState(mInstantTetherEnabled, + pendingIntent, mExtras); + } + return new SharedConnectivitySettingsState(mInstantTetherEnabled, null, mExtras); } } - private SharedConnectivitySettingsState(boolean instantTetherEnabled, Bundle extras) { + private SharedConnectivitySettingsState(boolean instantTetherEnabled, + PendingIntent pendingIntent, Bundle extras) { + mInstantTetherEnabled = instantTetherEnabled; + mInstantTetherSettingsPendingIntent = pendingIntent; mExtras = extras; } @@ -96,6 +129,16 @@ public final class SharedConnectivitySettingsState implements Parcelable { } /** + * Gets the pending intent to open Instant Tether settings page. + * + * @return Returns the pending intent that opens the settings page, null if none. + */ + @Nullable + public PendingIntent getInstantTetherSettingsPendingIntent() { + return mInstantTetherSettingsPendingIntent; + } + + /** * Gets the extras Bundle. * * @return Returns a Bundle object. @@ -109,12 +152,14 @@ public final class SharedConnectivitySettingsState implements Parcelable { public boolean equals(Object obj) { if (!(obj instanceof SharedConnectivitySettingsState)) return false; SharedConnectivitySettingsState other = (SharedConnectivitySettingsState) obj; - return mInstantTetherEnabled == other.isInstantTetherEnabled(); + return mInstantTetherEnabled == other.isInstantTetherEnabled() + && Objects.equals(mInstantTetherSettingsPendingIntent, + other.getInstantTetherSettingsPendingIntent()); } @Override public int hashCode() { - return Objects.hash(mInstantTetherEnabled); + return Objects.hash(mInstantTetherEnabled, mInstantTetherSettingsPendingIntent); } @Override @@ -124,6 +169,7 @@ public final class SharedConnectivitySettingsState implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { + mInstantTetherSettingsPendingIntent.writeToParcel(dest, 0); dest.writeBoolean(mInstantTetherEnabled); dest.writeBundle(mExtras); } @@ -135,8 +181,10 @@ public final class SharedConnectivitySettingsState implements Parcelable { */ @NonNull public static SharedConnectivitySettingsState readFromParcel(@NonNull Parcel in) { - return new SharedConnectivitySettingsState(in.readBoolean(), - in.readBundle()); + PendingIntent pendingIntent = PendingIntent.CREATOR.createFromParcel(in); + boolean instantTetherEnabled = in.readBoolean(); + Bundle extras = in.readBundle(); + return new SharedConnectivitySettingsState(instantTetherEnabled, pendingIntent, extras); } @NonNull @@ -156,6 +204,7 @@ public final class SharedConnectivitySettingsState implements Parcelable { public String toString() { return new StringBuilder("SharedConnectivitySettingsState[") .append("instantTetherEnabled=").append(mInstantTetherEnabled) + .append("PendingIntent=").append(mInstantTetherSettingsPendingIntent.toString()) .append("extras=").append(mExtras.toString()) .append("]").toString(); } diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java index 57108e4aa227..87ca99fd3e03 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java @@ -68,9 +68,7 @@ public abstract class SharedConnectivityService extends Service { new RemoteCallbackList<>(); private List<HotspotNetwork> mHotspotNetworks = Collections.emptyList(); private List<KnownNetwork> mKnownNetworks = Collections.emptyList(); - private SharedConnectivitySettingsState mSettingsState = - new SharedConnectivitySettingsState.Builder().setInstantTetherEnabled(false) - .setExtras(Bundle.EMPTY).build(); + private SharedConnectivitySettingsState mSettingsState = null; private HotspotNetworkConnectionStatus mHotspotNetworkConnectionStatus = new HotspotNetworkConnectionStatus.Builder() .setStatus(HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN) @@ -202,6 +200,13 @@ public abstract class SharedConnectivityService extends Service { @Override public SharedConnectivitySettingsState getSettingsState() { checkPermissions(); + // Done lazily since creating it needs a context. + if (mSettingsState == null) { + mSettingsState = new SharedConnectivitySettingsState + .Builder(getApplicationContext()) + .setInstantTetherEnabled(false) + .setExtras(Bundle.EMPTY).build(); + } return mSettingsState; } diff --git a/wifi/tests/Android.bp b/wifi/tests/Android.bp index c9105f7454e4..7a299694741a 100644 --- a/wifi/tests/Android.bp +++ b/wifi/tests/Android.bp @@ -36,6 +36,7 @@ android_test { static_libs: [ "androidx.test.rules", + "androidx.test.core", "frameworks-base-testutils", "guava", "mockito-target-minus-junit4", 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 7578dfd11225..71239087b2bd 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java @@ -497,8 +497,9 @@ public class SharedConnectivityManagerTest { @Test public void getSettingsState_serviceConnected_shouldReturnState() throws RemoteException { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); - SharedConnectivitySettingsState state = new SharedConnectivitySettingsState.Builder() - .setInstantTetherEnabled(true).setExtras(new Bundle()).build(); + SharedConnectivitySettingsState state = + new SharedConnectivitySettingsState.Builder(mContext).setInstantTetherEnabled(true) + .setExtras(new Bundle()).build(); manager.setService(mService); when(mService.getSettingsState()).thenReturn(state); diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java index 752b74905c97..5e17dfba9790 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java @@ -18,8 +18,10 @@ package android.net.wifi.sharedconnectivity.app; import static com.google.common.truth.Truth.assertThat; +import android.content.Intent; import android.os.Parcel; +import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; import org.junit.Test; @@ -30,8 +32,12 @@ import org.junit.Test; @SmallTest public class SharedConnectivitySettingsStateTest { private static final boolean INSTANT_TETHER_STATE = true; + private static final String INTENT_ACTION = "instant.tether.settings"; private static final boolean INSTANT_TETHER_STATE_1 = false; + private static final String INTENT_ACTION_1 = "instant.tether.settings1"; + + /** * Verifies parcel serialization/deserialization. */ @@ -39,16 +45,11 @@ public class SharedConnectivitySettingsStateTest { public void testParcelOperation() { SharedConnectivitySettingsState state = buildSettingsStateBuilder().build(); - Parcel parcelW = Parcel.obtain(); - state.writeToParcel(parcelW, 0); - byte[] bytes = parcelW.marshall(); - parcelW.recycle(); - - Parcel parcelR = Parcel.obtain(); - parcelR.unmarshall(bytes, 0, bytes.length); - parcelR.setDataPosition(0); + Parcel parcel = Parcel.obtain(); + state.writeToParcel(parcel, 0); + parcel.setDataPosition(0); SharedConnectivitySettingsState fromParcel = - SharedConnectivitySettingsState.CREATOR.createFromParcel(parcelR); + SharedConnectivitySettingsState.CREATOR.createFromParcel(parcel); assertThat(fromParcel).isEqualTo(state); assertThat(fromParcel.hashCode()).isEqualTo(state.hashCode()); @@ -66,6 +67,10 @@ public class SharedConnectivitySettingsStateTest { SharedConnectivitySettingsState.Builder builder = buildSettingsStateBuilder() .setInstantTetherEnabled(INSTANT_TETHER_STATE_1); assertThat(builder.build()).isNotEqualTo(state1); + + builder = buildSettingsStateBuilder() + .setInstantTetherSettingsPendingIntent(new Intent(INTENT_ACTION_1)); + assertThat(builder.build()).isNotEqualTo(state1); } /** @@ -86,7 +91,9 @@ public class SharedConnectivitySettingsStateTest { } private SharedConnectivitySettingsState.Builder buildSettingsStateBuilder() { - return new SharedConnectivitySettingsState.Builder() - .setInstantTetherEnabled(INSTANT_TETHER_STATE); + return new SharedConnectivitySettingsState.Builder( + ApplicationProvider.getApplicationContext()) + .setInstantTetherEnabled(INSTANT_TETHER_STATE) + .setInstantTetherSettingsPendingIntent(new Intent(INTENT_ACTION)); } } diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java index b8b6b767eed3..514ba3c0472e 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java @@ -75,9 +75,6 @@ public class SharedConnectivityServiceTest { .addSecurityType(SECURITY_TYPE_EAP).setNetworkProviderInfo( NETWORK_PROVIDER_INFO).build(); private static final List<KnownNetwork> KNOWN_NETWORKS = List.of(KNOWN_NETWORK); - private static final SharedConnectivitySettingsState SETTINGS_STATE = - new SharedConnectivitySettingsState.Builder().setInstantTetherEnabled(true) - .setExtras(Bundle.EMPTY).build(); private static final HotspotNetworkConnectionStatus TETHER_NETWORK_CONNECTION_STATUS = new HotspotNetworkConnectionStatus.Builder().setStatus(CONNECTION_STATUS_UNKNOWN) .setHotspotNetwork(HOTSPOT_NETWORK).setExtras(Bundle.EMPTY).build(); @@ -155,10 +152,11 @@ public class SharedConnectivityServiceTest { SharedConnectivityService service = createService(); ISharedConnectivityService.Stub binder = (ISharedConnectivityService.Stub) service.onBind(new Intent()); + when(mContext.getPackageName()).thenReturn("android.net.wifi.nonupdatable.test"); - service.setSettingsState(SETTINGS_STATE); + service.setSettingsState(buildSettingsState()); - assertThat(binder.getSettingsState()).isEqualTo(SETTINGS_STATE); + assertThat(binder.getSettingsState()).isEqualTo(buildSettingsState()); } @Test @@ -232,4 +230,10 @@ public class SharedConnectivityServiceTest { service.attachBaseContext(mContext); return service; } + + private SharedConnectivitySettingsState buildSettingsState() { + return new SharedConnectivitySettingsState.Builder(mContext).setInstantTetherEnabled(true) + .setInstantTetherSettingsPendingIntent(new Intent()) + .setExtras(Bundle.EMPTY).build(); + } } |